@@ -2242,6 +2242,9 @@ func (s *GatewayService) isAccountSchedulableForRPM(ctx context.Context, account
22422242}
22432243
22442244// IncrementAccountRPM increments the RPM counter for the given account.
2245+ // 已知 TOCTOU 竞态:调度时读取 RPM 计数与此处递增之间存在时间窗口,
2246+ // 高并发下可能短暂超出 RPM 限制。这是与 WindowCost 一致的 soft-limit
2247+ // 设计权衡——可接受的少量超额优于加锁带来的延迟和复杂度。
22452248func (s * GatewayService ) IncrementAccountRPM (ctx context.Context , accountID int64 ) error {
22462249 if s .rpmCache == nil {
22472250 return nil
@@ -2444,7 +2447,7 @@ func sameAccountWithLoadGroup(a, b accountWithLoad) bool {
24442447// shuffleWithinPriorityAndLastUsed 对排序后的 []*Account 切片,按 (Priority, LastUsedAt) 分组后组内随机打乱。
24452448//
24462449// 注意:当 preferOAuth=true 时,需要保证 OAuth 账号在同组内仍然优先,否则会把排序时的偏好打散掉。
2447- // 因此这里采用“ 组内分区 + 分区内 shuffle” 的方式:
2450+ // 因此这里采用" 组内分区 + 分区内 shuffle" 的方式:
24482451// - 先把同组账号按 (OAuth / 非 OAuth) 拆成两段,保持 OAuth 段在前;
24492452// - 再分别在各段内随机打散,避免热点。
24502453func shuffleWithinPriorityAndLastUsed (accounts []* Account , preferOAuth bool ) {
@@ -2584,7 +2587,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
25842587 if clearSticky {
25852588 _ = s .cache .DeleteSessionAccountID (ctx , derefGroupID (groupID ), sessionHash )
25862589 }
2587- if ! clearSticky && s .isAccountInGroup (account , groupID ) && account .Platform == platform && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForRPM (ctx , account , true ) {
2590+ if ! clearSticky && s .isAccountInGroup (account , groupID ) && account .Platform == platform && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM (ctx , account , true ) {
25882591 if s .debugModelRoutingEnabled () {
25892592 logger .LegacyPrintf ("service.gateway" , "[ModelRoutingDebug] legacy routed sticky hit: group_id=%v model=%s session=%s account=%d" , derefGroupID (groupID ), requestedModel , shortSessionHash (sessionHash ), accountID )
25902593 }
@@ -2607,7 +2610,8 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
26072610 }
26082611 accountsLoaded = true
26092612
2610- // 提前预取 RPM 计数,确保 routing 段内的 isAccountSchedulableForRPM 调用能命中缓存
2613+ // 提前预取窗口费用+RPM 计数,确保 routing 段内的调度检查调用能命中缓存
2614+ ctx = s .withWindowCostPrefetch (ctx , accounts )
26112615 ctx = s .withRPMPrefetch (ctx , accounts )
26122616
26132617 routingSet := make (map [int64 ]struct {}, len (routingAccountIDs ))
@@ -2690,7 +2694,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
26902694 if clearSticky {
26912695 _ = s .cache .DeleteSessionAccountID (ctx , derefGroupID (groupID ), sessionHash )
26922696 }
2693- if ! clearSticky && s .isAccountInGroup (account , groupID ) && account .Platform == platform && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForRPM (ctx , account , true ) {
2697+ if ! clearSticky && s .isAccountInGroup (account , groupID ) && account .Platform == platform && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM (ctx , account , true ) {
26942698 return account , nil
26952699 }
26962700 }
@@ -2711,7 +2715,8 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
27112715 }
27122716 }
27132717
2714- // 批量预取 RPM 计数,避免逐个账号查询(N+1)
2718+ // 批量预取窗口费用+RPM 计数,避免逐个账号查询(N+1)
2719+ ctx = s .withWindowCostPrefetch (ctx , accounts )
27152720 ctx = s .withRPMPrefetch (ctx , accounts )
27162721
27172722 // 3. 按优先级+最久未用选择(考虑模型支持)
@@ -2804,7 +2809,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
28042809 if clearSticky {
28052810 _ = s .cache .DeleteSessionAccountID (ctx , derefGroupID (groupID ), sessionHash )
28062811 }
2807- if ! clearSticky && s .isAccountInGroup (account , groupID ) && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForRPM (ctx , account , true ) {
2812+ if ! clearSticky && s .isAccountInGroup (account , groupID ) && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM (ctx , account , true ) {
28082813 if account .Platform == nativePlatform || (account .Platform == PlatformAntigravity && account .IsMixedSchedulingEnabled ()) {
28092814 if s .debugModelRoutingEnabled () {
28102815 logger .LegacyPrintf ("service.gateway" , "[ModelRoutingDebug] legacy mixed routed sticky hit: group_id=%v model=%s session=%s account=%d" , derefGroupID (groupID ), requestedModel , shortSessionHash (sessionHash ), accountID )
@@ -2825,7 +2830,8 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
28252830 }
28262831 accountsLoaded = true
28272832
2828- // 提前预取 RPM 计数,确保 routing 段内的 isAccountSchedulableForRPM 调用能命中缓存
2833+ // 提前预取窗口费用+RPM 计数,确保 routing 段内的调度检查调用能命中缓存
2834+ ctx = s .withWindowCostPrefetch (ctx , accounts )
28292835 ctx = s .withRPMPrefetch (ctx , accounts )
28302836
28312837 routingSet := make (map [int64 ]struct {}, len (routingAccountIDs ))
@@ -2912,7 +2918,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
29122918 if clearSticky {
29132919 _ = s .cache .DeleteSessionAccountID (ctx , derefGroupID (groupID ), sessionHash )
29142920 }
2915- if ! clearSticky && s .isAccountInGroup (account , groupID ) && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForRPM (ctx , account , true ) {
2921+ if ! clearSticky && s .isAccountInGroup (account , groupID ) && (requestedModel == "" || s .isModelSupportedByAccountWithContext (ctx , account , requestedModel )) && s .isAccountSchedulableForModelSelection (ctx , account , requestedModel ) && s .isAccountSchedulableForWindowCost ( ctx , account , true ) && s . isAccountSchedulableForRPM (ctx , account , true ) {
29162922 if account .Platform == nativePlatform || (account .Platform == PlatformAntigravity && account .IsMixedSchedulingEnabled ()) {
29172923 return account , nil
29182924 }
@@ -2931,7 +2937,8 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
29312937 }
29322938 }
29332939
2934- // 批量预取 RPM 计数,避免逐个账号查询(N+1)
2940+ // 批量预取窗口费用+RPM 计数,避免逐个账号查询(N+1)
2941+ ctx = s .withWindowCostPrefetch (ctx , accounts )
29352942 ctx = s .withRPMPrefetch (ctx , accounts )
29362943
29372944 // 3. 按优先级+最久未用选择(考虑模型支持和混合调度)
@@ -5304,7 +5311,7 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool {
53045311}
53055312
53065313func (s * GatewayService ) shouldFailoverOn400 (respBody []byte ) bool {
5307- // 只对“ 可能是兼容性差异导致” 的 400 允许切换,避免无意义重试。
5314+ // 只对" 可能是兼容性差异导致" 的 400 允许切换,避免无意义重试。
53085315 // 默认保守:无法识别则不切换。
53095316 msg := strings .ToLower (strings .TrimSpace (extractUpstreamErrorMessage (respBody )))
53105317 if msg == "" {
0 commit comments