fix(routing): harden routed pool scheduling

This commit is contained in:
elky
2026-07-27 22:06:28 +08:00
parent 550cc36760
commit 4148ab1931
62 changed files with 5510 additions and 850 deletions
@@ -1,22 +1,27 @@
import { describe, expect, it } from 'vitest'
import { moveStrategyItem } from '@/features/pool/utils/poolSchedulingDialog'
import {
mergePoolAdvancedPatch,
moveStrategyItem,
normalizeMutexSelection,
} from '@/features/pool/utils/poolSchedulingDialog'
interface TestPresetItem {
preset: string
mutexGroup: string | null
enabled: boolean
applicable: boolean
}
function buildItems(): TestPresetItem[] {
return [
{ preset: 'cache_affinity', mutexGroup: 'distribution_mode', enabled: false },
{ preset: 'lru', mutexGroup: 'distribution_mode', enabled: true },
{ preset: 'single_account', mutexGroup: 'distribution_mode', enabled: false },
{ preset: 'load_balance', mutexGroup: 'distribution_mode', enabled: false },
{ preset: 'recent_refresh', mutexGroup: null, enabled: true },
{ preset: 'quota_balanced', mutexGroup: null, enabled: false },
{ preset: 'priority_first', mutexGroup: null, enabled: true },
{ preset: 'cache_affinity', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
{ preset: 'lru', mutexGroup: 'distribution_mode', enabled: true, applicable: true },
{ preset: 'single_account', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
{ preset: 'load_balance', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
{ preset: 'recent_refresh', mutexGroup: null, enabled: true, applicable: true },
{ preset: 'quota_balanced', mutexGroup: null, enabled: false, applicable: true },
{ preset: 'priority_first', mutexGroup: null, enabled: true, applicable: true },
]
}
@@ -69,4 +74,49 @@ describe('poolSchedulingDialog', () => {
expect(moved.map(item => item.preset)).toEqual(original.map(item => item.preset))
})
it('keeps the first enabled distribution from the saved order', () => {
const items = buildItems()
items[0].enabled = true
items[1].enabled = false
items[3].enabled = true
const savedOrder = [items[3], items[0], ...items.slice(1, 3), ...items.slice(4)]
const normalized = normalizeMutexSelection(savedOrder)
expect(normalized.find(item => item.preset === 'load_balance')?.enabled).toBe(true)
expect(normalized.find(item => item.preset === 'cache_affinity')?.enabled).toBe(false)
})
it('does not invent a distribution mode when all are disabled', () => {
const items = buildItems().map(item => ({
...item,
enabled: item.mutexGroup ? false : item.enabled,
}))
const normalized = normalizeMutexSelection(items)
expect(normalized.filter(item => item.mutexGroup).every(item => !item.enabled)).toBe(true)
})
it('preserves pool fields that the current dialog does not edit', () => {
const merged = mergePoolAdvancedPatch({
sticky_session_ttl_seconds: 900,
cost_window_seconds: 7200,
cost_limit_per_key_tokens: 100_000,
probing_target_percent: 25,
global_priority: 7,
}, {
score_top_n: 256,
})
expect(merged).toEqual({
sticky_session_ttl_seconds: 900,
cost_window_seconds: 7200,
cost_limit_per_key_tokens: 100_000,
probing_target_percent: 25,
global_priority: 7,
score_top_n: 256,
})
})
})
@@ -2,6 +2,47 @@ export interface SchedulingDialogPresetLike {
mutexGroup: string | null
}
export interface SchedulingDialogSelectablePresetLike extends SchedulingDialogPresetLike {
enabled: boolean
applicable: boolean
}
export function mergePoolAdvancedPatch(
current: unknown,
patch: Record<string, unknown>,
): Record<string, unknown> {
const currentRecord = typeof current === 'object' && current !== null && !Array.isArray(current)
? current as Record<string, unknown>
: {}
return {
...currentRecord,
...patch,
}
}
export function normalizeMutexSelection<T extends SchedulingDialogSelectablePresetLike>(
items: readonly T[],
): T[] {
const next = items.map(item => ({ ...item }))
const groups = new Map<string, number[]>()
next.forEach((item, index) => {
if (!item.mutexGroup) return
const indexes = groups.get(item.mutexGroup) ?? []
indexes.push(index)
groups.set(item.mutexGroup, indexes)
})
for (const indexes of groups.values()) {
const winner = indexes.find(index => next[index].enabled && next[index].applicable)
indexes.forEach((index) => {
next[index].enabled = winner !== undefined && index === winner && next[index].applicable
})
}
return next
}
export function moveStrategyItem<T extends SchedulingDialogPresetLike>(
items: readonly T[],
itemIndex: number,