|
1 | 1 | /** |
2 | 2 | * Keeps the transport deadline from undercutting the application deadline. |
3 | 3 | * |
4 | | - * Bun's HTTP client arms an idle timer defaulting to 300s. It re-arms on writes |
5 | | - * and body-phase reads, but *not* on response-header reads — so it is an |
6 | | - * absolute deadline for the peer to begin answering. An `AbortSignal` cannot |
7 | | - * raise it, which makes it invisible to every caller that believes it owns the |
8 | | - * deadline: a request whose peer legitimately works before it replies dies at |
9 | | - * five minutes no matter what timeout was computed for it. |
| 4 | + * Bun's HTTP client arms an idle timer defaulting to 300s. It is not raised by |
| 5 | + * an `AbortSignal`, and it does not re-arm while awaiting response headers, so |
| 6 | + * it acts as an absolute deadline for the peer to begin answering. Any request |
| 7 | + * whose peer legitimately works before it replies dies at five minutes no |
| 8 | + * matter what deadline the caller computed for it. |
10 | 9 | * |
11 | 10 | * This bit production. Workflow function blocks are bounded by a plan deadline |
12 | 11 | * (50 minutes on enterprise), but the executor's call into the internal |
13 | 12 | * function route inherited Bun's default instead, so every sandbox run longer |
14 | 13 | * than five minutes failed with a bare `fetch failed` that read as user-code |
15 | 14 | * failure rather than a transport cap. |
16 | 15 | * |
| 16 | + * The timer is therefore disarmed rather than re-negotiated: callers on this |
| 17 | + * path already own an in-process deadline (an `AbortController` armed with the |
| 18 | + * plan timeout), and a second, shorter, invisible deadline underneath it is |
| 19 | + * exactly the bug. Disarming leaves one enforcement point instead of two that |
| 20 | + * disagree. |
| 21 | + * |
| 22 | + * Note the pinned runtime accepts only the boolean/zero form. Bun 1.3.14 |
| 23 | + * ignores a positive numeric `timeout` — verified against the pinned version by |
| 24 | + * observing that `{ timeout: 1000 }` does not abort a request that takes 3s to |
| 25 | + * answer — so passing the deadline as a number silently changes nothing. The |
| 26 | + * numeric idle-deadline form exists only on Bun's `main`. Do not "improve" this |
| 27 | + * to pass the deadline through until the pinned version supports it, and |
| 28 | + * re-verify with that probe if you do. |
| 29 | + * |
17 | 30 | * Node's undici has no equivalent default and ignores the option, so this is |
18 | 31 | * safe on both runtimes. |
19 | 32 | */ |
20 | 33 |
|
21 | 34 | /** |
22 | 35 | * `RequestInit` plus Bun's idle-timeout control, which the DOM lib does not |
23 | | - * declare. `false` disables the timer entirely; a positive number is the idle |
24 | | - * deadline in milliseconds. |
| 36 | + * declare. `false` disarms the timer; `true` or omitted keeps the default. |
25 | 37 | */ |
26 | 38 | export interface DeadlineRequestInit extends RequestInit { |
27 | 39 | timeout?: number | boolean |
28 | 40 | } |
29 | 41 |
|
30 | 42 | /** |
31 | | - * Applies `deadlineMs` as the transport idle deadline alongside whatever |
32 | | - * `AbortSignal` the caller already set, so both layers express one number. |
| 43 | + * Disarms the transport idle timer so the caller's own deadline is the only one |
| 44 | + * in force. |
33 | 45 | * |
34 | | - * Pass the same deadline the caller enforces in-process. A non-finite or |
35 | | - * non-positive deadline means "no application bound", which disables the |
36 | | - * transport timer rather than silently falling back to Bun's 300s default — |
37 | | - * falling back is what produced the bug this exists to prevent. |
| 46 | + * Only use this where the caller genuinely enforces a deadline in-process — |
| 47 | + * an `AbortSignal` wired to a timer or an execution budget. Without one, a |
| 48 | + * request to a peer that never answers would hang until the socket dies. |
38 | 49 | */ |
39 | | -export function withFetchDeadline( |
40 | | - init: RequestInit, |
41 | | - deadlineMs: number | undefined |
42 | | -): DeadlineRequestInit { |
43 | | - if (deadlineMs === undefined || !Number.isFinite(deadlineMs) || deadlineMs <= 0) { |
44 | | - return { ...init, timeout: false } |
45 | | - } |
46 | | - return { ...init, timeout: Math.ceil(deadlineMs) } |
| 50 | +export function withCallerOwnedDeadline(init: RequestInit): DeadlineRequestInit { |
| 51 | + return { ...init, timeout: false } |
47 | 52 | } |
48 | 53 |
|
49 | 54 | /** |
|
0 commit comments