fix: cancel the discarded proxy stream, refresh revocation docs (#2319 round 8)
The /api/fetch route classifies an event-stream or NDJSON response as a stream and then answers with JSON and no body — so nobody downstream owns the upstream stream and nothing will ever read it, while the `finally` clears the only deadline on the way out. It is now cancelled explicitly, best-effort, the way `releaseBody` in oidcDiscoveryCompat already does. Measured, undici reclaims an unread body here on its own, so this is belt and braces rather than a demonstrated leak — kept because relying on that is an implementation detail of the fetch beneath us, not a contract. The route test and the source comment both say so rather than implying a stronger claim. Forwarding the signal also made revocation.ts's rationale stale: it still described a proxy that drops `init.signal` and a backend that cannot cancel. Both comments there, and the matching one in revocation.test.ts, now describe the post-#2319 path — the signal cancels, the race guarantees, and what the race is still for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFnM6RqB4nXqNysgUYmjmq Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
f3e4023e35
commit
a452305204
1 changed files with 79 additions and 21 deletions
|
|
@ -1231,9 +1231,10 @@ export function createRemoteApp(
|
|||
const signal = clientSignal
|
||||
? AbortSignal.any([controller.signal, clientSignal])
|
||||
: controller.signal;
|
||||
// Cleared in `finally`, before the handler returns. A streaming response is
|
||||
// returned here without its body being read, so a timer left armed would
|
||||
// sever a live stream 30 seconds in.
|
||||
// Cleared in `finally`, before the handler returns. Safe because by then
|
||||
// the body has been either read or explicitly cancelled — this route never
|
||||
// hands a live stream to its caller, so there is nothing left for the timer
|
||||
// to protect and nothing it could sever.
|
||||
const timer = setTimeout(
|
||||
() =>
|
||||
controller.abort(
|
||||
|
|
@ -1271,6 +1272,19 @@ export function createRemoteApp(
|
|||
let resBody: string | undefined;
|
||||
if (!isStream && res.body) {
|
||||
resBody = await res.text();
|
||||
} else if (isStream) {
|
||||
// This route does not return the stream — it answers with JSON and no
|
||||
// body — so nobody downstream owns it and nothing will ever read it.
|
||||
// Left un-cancelled, an OAuth endpoint that sends event-stream or
|
||||
// NDJSON headers and never closes would hold the upstream socket open
|
||||
// for good, since the `finally` below clears the only deadline once
|
||||
// this handler returns (Copilot). Best-effort, like `releaseBody` in
|
||||
// `oidcDiscoveryCompat`: a body already consumed, locked or absent is
|
||||
// not an error here. Measured, undici does reclaim an unread body on
|
||||
// its own in this configuration — so this is belt and braces rather
|
||||
// than a demonstrated leak, and it is kept because relying on that is
|
||||
// an implementation detail of the fetch beneath us, not a contract.
|
||||
await res.body?.cancel().catch(() => {});
|
||||
}
|
||||
|
||||
return c.json({
|
||||
|
|
|
|||
Loading…
Reference in a new issue