Răsfoiți Sursa

fix: address hedge race conditions and audit accuracy in forwarder

- Add attempt.settled guard in .then() callback to prevent stale response processing
- Clear thresholdTimer before rectifier retry to avoid spurious hedge triggers
- Use requestAttemptCount instead of sequence for accurate retry chain entries
- Merge specialSettings on hedge winner sync to preserve rectifier audit data

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
ding113 3 săptămâni în urmă
părinte
comite
2409ecafb1
1 a modificat fișierele cu 15 adăugiri și 3 ștergeri
  1. 15 3
      src/app/v1/_lib/proxy/forwarder.ts

+ 15 - 3
src/app/v1/_lib/proxy/forwarder.ts

@@ -3092,7 +3092,7 @@ export class ProxyForwarder {
         attempt.requestAttemptCount
       )
         .then(async (response) => {
-          if (settled || winnerCommitted) {
+          if (settled || winnerCommitted || attempt.settled) {
             const attemptRuntime = attempt.session as ProxySessionWithAttemptRuntime;
             try {
               attemptRuntime.responseController?.abort(new Error("hedge_loser"));
@@ -3241,13 +3241,17 @@ export class ProxyForwarder {
             buildRetryFailedChainEntry(
               attempt.provider,
               attempt.endpointAudit,
-              attempt.sequence,
+              attempt.requestAttemptCount,
               error,
               errorMessage,
               reactiveRectifierResult.requestDetailsBeforeRectify
             )
           );
 
+          if (attempt.thresholdTimer) {
+            clearTimeout(attempt.thresholdTimer);
+            attempt.thresholdTimer = null;
+          }
           attempt.requestAttemptCount += 1;
           runAttempt(attempt);
           return;
@@ -3670,7 +3674,15 @@ export class ProxyForwarder {
       }
     }
     targetState.providerChain = mergedProviderChain;
-    targetState.specialSettings = [...sourceState.specialSettings];
+    // 合并 specialSettings,避免覆盖已有的 rectifier audit 记录
+    const existingKeys = new Set(targetState.specialSettings.map((s) => JSON.stringify(s)));
+    const merged = [...targetState.specialSettings];
+    for (const setting of sourceState.specialSettings) {
+      if (!existingKeys.has(JSON.stringify(setting))) {
+        merged.push(setting);
+      }
+    }
+    targetState.specialSettings = merged;
     targetState.originalModelName = sourceState.originalModelName;
     targetState.originalUrlPathname = sourceState.originalUrlPathname;
     targetState.clearResponseTimeout = sourceRuntime.clearResponseTimeout;