fetch/S3: replace ResumableSink with proper JSSinks - #36087
Code review found 1 important issue
Found 4 candidates, confirmed 3. See review comments for details.
Details
| Severity | Count |
|---|---|
| 🔴 Important | 1 |
| 🟡 Nit | 2 |
| 🟣 Pre-existing | 0 |
| Severity | File:Line | Issue |
|---|---|---|
| 🔴 Important | src/runtime/api/ResumableSink.classes.ts:27-38 |
controller.end(value) on the direct ResumableSink path aborts the upload instead of cleanly closing |
| 🟡 Nit | src/runtime/webcore/ResumableSink.rs:512-516 |
js_end/js_close clear the flushPromise slot without settling it |
| 🟡 Nit | src/jsc/bindings/webcore/streams/BunStreamSource.cpp:1564-1568 |
resumableSetupDirect: !pull.isCallable() branch returns without releasing/checking the ThrowScope |
Annotations
Check failure on line 38 in src/runtime/api/ResumableSink.classes.ts
claude / Claude Code Review
controller.end(value) on the direct ResumableSink path aborts the upload instead of cleanly closing
On `JSDirectStreamController`, `end` and `close` both bind to `boundDirectClose` (clean close — the arg is a close-reason, not an error), so a user `pull()` calling `controller.end(x)` cleanly finishes the upload. This PR now hands the ResumableSink itself to that same `pull()`, but here `end` maps to `jsEnd` (the same target as the new `error` alias), so `controller.end('done')` — or even `controller.end(undefined)` — reaches `write_end_request(Some(...))` → `abort_task()` and the fetch/S3 uplo
Check warning on line 516 in src/runtime/webcore/ResumableSink.rs
claude / Claude Code Review
js_end/js_close clear the flushPromise slot without settling it
`js_end` and `js_close` call `detach_js()`, which zeroes the new `flushPromise` slot at line 515 without settling it — unlike `drain()` and `cancel()`, which `take_flush_promise()` and resolve/reject first. A user `type:"direct"` pull that does `write()→false; const p = controller.flush(true); controller.close(); await p` will hang on `p` forever. Mirror `cancel()`: `take_flush_promise()` + resolve (clean close) / reject (end-with-error) before `detach_js()`.
Check warning on line 1568 in src/jsc/bindings/webcore/streams/BunStreamSource.cpp
claude / Claude Code Review
resumableSetupDirect: !pull.isCallable() branch returns without releasing/checking the ThrowScope
The `!pull.isCallable()` branch calls `invokeMethod(..., endPublicName, ...)` then bare-`return;`s without `scope.release()` or a trailing `RETURN_IF_EXCEPTION`, so the outer ThrowScope destructs unreleased and asserts under `BUN_JSC_validateExceptionChecks=1`. Every sibling exit in this file follows the convention (`readDirectStream`'s equivalent branch has `RETURN_IF_EXCEPTION` after the close call; `resumableSetup`'s tail is `RELEASE_AND_RETURN`) — add `scope.release();` before the `invokeMet