fix: streaming reliability, timeout, memory leaks, and retry improvements

- Fix streaming timeout: use AbortController for connection-only timeout
  instead of AbortSignal.timeout() that kills active SSE streams
- Fix fetchViaCurl: stream body via ReadableStream instead of buffering
  entire response in memory
- Fix JWT/aichat race condition: add Promise dedup to prevent concurrent
  bootstrap calls (10 requests = 1 bootstrap, not 10)
- Fix ACTIVE_READERS memory leak: auto-remove readers on stream completion
- Fix WebSocket backpressure: log warning when client buffer exceeds 1MB
- Add SSE heartbeat/keepalive: send ': keepalive' every 15s to prevent
  LB/proxy timeout during AI thinking
- Fix SSELineBuffer: graceful overflow handling (warn + discard instead
  of throwing error that crashes stream)
- Fix transformStream tight loop: yield to event loop after each chunk
  to prevent starvation
- Fix fetchViaCurl process cleanup: use SIGKILL + proper timeout cleanup
- Add retry on 502/504: retry transient server errors before returning
  to caller (both fetchWithRetry and fetchWithSessionRetry)
This commit is contained in:
MythEclipse
2026-06-20 17:13:32 +07:00
parent 35c0123529
commit c62de0899a
9 changed files with 231 additions and 50 deletions
+9 -1
View File
@@ -18,6 +18,7 @@ import * as os from "node:os";
let cachedJwt: string | null = null;
let jwtExpiry = 0; // epoch ms
let pendingRefresh: Promise<string> | null = null; // dedup concurrent refreshes
const MIMO_BOOTSTRAP_URL = "https://api.xiaomimimo.com/api/free-ai/bootstrap";
const EXPIRY_BUFFER_MS = 300_000; // 5 minutes
@@ -112,7 +113,13 @@ export async function getJwt(): Promise<string> {
if (cachedJwt && jwtExpiry > now + EXPIRY_BUFFER_MS) {
return cachedJwt;
}
return bootstrapJwt();
// Dedup concurrent refresh — all callers share the same Promise
if (!pendingRefresh) {
pendingRefresh = bootstrapJwt().finally(() => {
pendingRefresh = null;
});
}
return pendingRefresh;
}
/**
@@ -124,4 +131,5 @@ export async function getJwt(): Promise<string> {
export function invalidateJwt(): void {
cachedJwt = null;
jwtExpiry = 0;
pendingRefresh = null;
}