fix: keep request timeouts active while reading response bodies - #1711
Merged
Merged
Conversation
Keep the request timeout active while the response body is read so a response that stalls after headers cannot hang indefinitely. Preserve existing non-2xx retry handling, ParseError behaviour and the original Response object. Successful-body timeouts are reported but are not automatically retried. Only the SDK read that owns the body disarms the deadline; a competing toJSON() or raw consumer cannot release it. On the successful-body path only the deadline's own expiry is reported as a timeout, and the WorkOS client translates only that 408 so other body-read errors propagate unchanged. Fixes workos#1679
Contributor
|
…empt The body-timeout fix kept the body lazy so getRawResponse() could hand back an unconsumed Response, and paid for it with a deadline that had to be handed from the attempt to the response object: a RequestTimeout interface plus null object, ref/unref bookkeeping, competing-reader detection, two AbortError policies and a 408 translation layer in WorkOS. Nothing reads raw bodies, and the API is JSON-only. Reading res.text() before clearing the existing timer covers the same stall with the timer we already had. The 408 then leaves fetchRequest() like any other timeout, so the retry policy, Idempotency-Key handling and handleHttpError() apply unchanged, and bodies nobody reads (delete, non-JSON) are drained instead of left on the connection. The timeout is now detected on the attempt's own signal rather than the error's name: the AbortError undici raises for an aborted body can come from another realm, where instanceof Error is false.
… out Reading the whole body inside the retryable attempt meant a 2xx response whose body stalled or failed re-entered the retry policy. The server had already applied that request, and PUT, PATCH and DELETE carry no idempotency key, so the retry could apply a mutation twice or turn a completed DELETE into a 404. Keep the error-body read inside the attempt, where the existing retry policy and Retry-After handling belong, but start the successful-body read as a promise handed to the response: it still runs under the attempt's deadline and a stall still surfaces as a 408 from toJSON(), translated by WorkOS like a timeout before the headers, but the retry loop never sees it. A body nobody awaits is still drained.
…nse as read toJSON() returned null for a non-JSON content type without observing the body read, so a stalled non-JSON body resolved the call early and its 408 was swallowed. Await the body first. delete() and deleteWithBody() now consume the response the same way, so every WorkOS request waits for the full body the timeout is documented to cover.
Routing delete() and deleteWithBody() through toJSON() made a successful deletion depend on its body parsing as JSON, so an empty or malformed body labelled application/json would have surfaced as a ParseError. Neither method uses the body; they resolve once the response arrives, as before, while the body is still drained in the background and bounded by the attempt's deadline.
Contributor
|
thanks ! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requests could hang indefinitely when a server returned headers but stalled the response body.
Keep the original per-attempt timeout active through body consumption. Preserve the original response object, prevent competing readers from disarming another reader’s deadline, and leave unrelated body-read errors unchanged.
Timeouts while reading error responses remain subject to the existing retry policy, preserving Retry-After and POST idempotency keys. Timeouts while reading successful responses are reported as 408 errors but are not automatically retried.
Validated with deterministic regressions and real HTTP tests using native Fetch and node-fetch 2.7.0. The full suite passes with 1,061 tests, alongside typecheck, lint, formatting and build checks.
Fixes #1679.