mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Claim scheduler-acquired slots before media eligibility checks and task-owner
validation. Release abandoned attempts immediately and retain once-only
cleanup on cancellation and forwarding completion.
Select only the resolved video owner, reuse bounded account waiting without
sticky escape, and preserve the task binding and TTL. Keep generation
failover and owner/billing isolation intact.
Regression tests reproduce unreleased account slots on upstream ab99d56e9
and pass with this change. Validation: go test ./..., unit and integration
suites, focused race tests, backend build, and golangci-lint (0 issues).
111 lines
3.9 KiB
Go
111 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSelectGrokMediaVideoRequestAccountPreservesOwner(t *testing.T) {
|
|
for _, state := range []string{"available", "full", "unavailable", "wrong group", "missing", "invalid id"} {
|
|
t.Run(state, func(t *testing.T) {
|
|
groupID := int64(24)
|
|
ownerID := int64(1)
|
|
owner := Account{ID: ownerID, Platform: PlatformGrok, Type: AccountTypeAPIKey,
|
|
Status: StatusActive, Schedulable: true, Concurrency: 50, GroupIDs: []int64{groupID}}
|
|
other := owner
|
|
other.ID = 2
|
|
if state == "unavailable" {
|
|
until := time.Now().Add(time.Minute)
|
|
owner.TempUnschedulableUntil = &until
|
|
}
|
|
if state == "wrong group" {
|
|
owner.GroupIDs = []int64{25}
|
|
}
|
|
accounts := []Account{owner, other}
|
|
if state == "missing" {
|
|
accounts = []Account{other}
|
|
}
|
|
if state == "invalid id" {
|
|
ownerID = 0
|
|
}
|
|
var acquired, released []int64
|
|
cache := &schedulerTestGatewayCache{}
|
|
cfg := &config.Config{}
|
|
cfg.Gateway.OpenAIScheduler.StickyEscapeEnabled = true
|
|
cfg.Gateway.Scheduling.StickySessionWaitTimeout = time.Second
|
|
cfg.Gateway.Scheduling.StickySessionMaxWaiting = 3
|
|
svc := &OpenAIGatewayService{
|
|
accountRepo: schedulerTestOpenAIAccountRepo{accounts: accounts}, cache: cache, cfg: cfg,
|
|
concurrencyService: NewConcurrencyService(schedulerTestConcurrencyCache{
|
|
acquireResults: map[int64]bool{1: state != "full", 2: true},
|
|
acquiredIDs: &acquired, releasedIDs: &released,
|
|
}),
|
|
}
|
|
ctx := context.Background()
|
|
require.NoError(t, svc.BindGrokMediaVideoRequestAccount(ctx, &groupID, "task", 10, 20, 1))
|
|
sessionHash := GrokMediaVideoRequestSessionHash("task", 10, 20)
|
|
for range 20 {
|
|
selection, decision, err := svc.SelectGrokMediaVideoRequestAccount(ctx, &groupID, sessionHash, ownerID, "")
|
|
switch state {
|
|
case "available":
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), selection.Account.ID)
|
|
require.True(t, selection.Acquired)
|
|
require.True(t, decision.StickySessionHit)
|
|
selection.ReleaseFunc()
|
|
case "full":
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), selection.Account.ID)
|
|
require.False(t, selection.Acquired)
|
|
require.Nil(t, selection.ReleaseFunc)
|
|
require.Equal(t, int64(1), selection.WaitPlan.AccountID)
|
|
default:
|
|
require.ErrorIs(t, err, ErrNoAvailableAccounts)
|
|
require.Nil(t, selection)
|
|
}
|
|
bound, err := svc.ResolveGrokMediaVideoRequestAccount(ctx, &groupID, "task", 10, 20)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(1), bound)
|
|
}
|
|
require.NotContains(t, acquired, int64(2))
|
|
require.Empty(t, cache.deletedSessions)
|
|
if state == "available" {
|
|
require.Len(t, released, 20)
|
|
} else {
|
|
require.Empty(t, released)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGrokVideoStickySelectionIgnoresHealthEscape(t *testing.T) {
|
|
groupID := int64(24)
|
|
account := Account{ID: 1, Platform: PlatformGrok, Type: AccountTypeAPIKey,
|
|
Status: StatusActive, Schedulable: true, Concurrency: 50, GroupIDs: []int64{groupID}}
|
|
svc := &OpenAIGatewayService{
|
|
accountRepo: schedulerTestOpenAIAccountRepo{accounts: []Account{account}},
|
|
cache: &schedulerTestGatewayCache{},
|
|
}
|
|
stats := newOpenAIAccountRuntimeStats()
|
|
for range 20 {
|
|
stats.report(1, false, nil)
|
|
}
|
|
scheduler := &defaultOpenAIAccountScheduler{service: svc, stats: stats}
|
|
req := OpenAIAccountScheduleRequest{GroupID: &groupID, Platform: PlatformGrok,
|
|
SessionHash: "task", StickyAccountID: 1, PreserveStickyBinding: true}
|
|
selection, escaped, err := scheduler.selectBySessionHash(context.Background(), req)
|
|
require.NoError(t, err)
|
|
require.Nil(t, selection)
|
|
require.True(t, escaped)
|
|
req.DisableStickyEscape = true
|
|
selection, escaped, err = scheduler.selectBySessionHash(context.Background(), req)
|
|
require.NoError(t, err)
|
|
require.False(t, escaped)
|
|
require.True(t, selection.Acquired)
|
|
selection.ReleaseFunc()
|
|
}
|