Skip to content

Commit 68398fd

Browse files
committed
fix: distinguish stall exhaustion errors
1 parent 8149b96 commit 68398fd

2 files changed

Lines changed: 11 additions & 10 deletions

File tree

packages/core/src/v3/apiClient/runStream-retries.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe("SSE retry exhaustion", () => {
8383
};
8484
const reader = await open({ stallTimeoutMs: 100, maxRetries: Infinity, maxStallRetries: 2 });
8585

86-
await expect(reader.read()).rejects.toThrow("Stream connection retries exhausted");
86+
await expect(reader.read()).rejects.toThrow("Stream stalled: no records received");
8787
expect(attempts).toBe(3);
8888
});
8989

@@ -118,7 +118,7 @@ describe("SSE retry exhaustion", () => {
118118
};
119119
const reader = await open({ stallTimeoutMs: 100, maxRetries: Infinity, maxStallRetries: 2 });
120120

121-
await expect(reader.read()).rejects.toThrow("Stream connection retries exhausted");
121+
await expect(reader.read()).rejects.toThrow("Stream stalled: no records received");
122122
expect(attempts).toBe(3);
123123
});
124124

@@ -131,7 +131,7 @@ describe("SSE retry exhaustion", () => {
131131
const reader = await open({ stallTimeoutMs: 100, maxRetries: Infinity, maxStallRetries: 2 });
132132

133133
expect(await reader.read()).toMatchObject({ done: false, value: { chunk: { hello: 1 } } });
134-
await expect(reader.read()).rejects.toThrow("Stream connection retries exhausted");
134+
await expect(reader.read()).rejects.toThrow("Stream stalled: no records received");
135135
expect(attempts).toBe(5);
136136
});
137137

@@ -182,7 +182,7 @@ describe("SSE retry exhaustion", () => {
182182
stallTimeoutMs: 100,
183183
});
184184

185-
await expect(reader.read()).rejects.toThrow("Stream connection retries exhausted");
185+
await expect(reader.read()).rejects.toThrow("Stream stalled: no records received");
186186
expect(attempts).toBe(5);
187187
});
188188
});

packages/core/src/v3/apiClient/runStream.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export class SSEStreamSubscription implements StreamSubscription {
221221
private retryCount = 0;
222222
private stallCount = 0;
223223
private maxRetries: number;
224+
private maxStallRetries: number;
224225
private retryDelayMs: number;
225226
private maxRetryDelayMs: number;
226227
private retryJitter: number;
@@ -296,6 +297,7 @@ export class SSEStreamSubscription implements StreamSubscription {
296297
this.lastEventId = options.lastEventId;
297298
this.from = options.from ?? "beginning";
298299
this.maxRetries = options.maxRetries ?? Infinity;
300+
this.maxStallRetries = options.maxStallRetries ?? Infinity;
299301
this.retryDelayMs = options.retryDelayMs ?? 100;
300302
this.maxRetryDelayMs = options.maxRetryDelayMs ?? 5000;
301303
this.retryJitter = options.retryJitter ?? 0.5;
@@ -653,13 +655,12 @@ export class SSEStreamSubscription implements StreamSubscription {
653655
return;
654656
}
655657

656-
if (
657-
this.retryCount >= this.maxRetries ||
658-
this.stallCount > (this.options.maxStallRetries ?? Infinity)
659-
) {
658+
const stallsExhausted = this.stallCount > this.maxStallRetries;
659+
if (this.retryCount >= this.maxRetries || stallsExhausted) {
660660
// Internal timeouts are failures, not caller cancellation.
661-
const finalError =
662-
error?.name === "AbortError"
661+
const finalError = stallsExhausted
662+
? new Error("Stream stalled: no records received")
663+
: error?.name === "AbortError"
663664
? new Error("Stream connection retries exhausted")
664665
: error || new Error("Max retries reached");
665666
controller.error(finalError);

0 commit comments

Comments
 (0)