Wait for off-thread jobs before freeing a terminated worker's VM - #36983
Closed
robobun wants to merge 14 commits into
Closed
Wait for off-thread jobs before freeing a terminated worker's VM#36983robobun wants to merge 14 commits into
robobun wants to merge 14 commits into
Claude / Claude Code Review
completed
Aug 5, 2026 in 36m 3s
Code review found 2 important issues
Found 2 candidates, confirmed 2. See review comments for details.
Details
| Severity | Count |
|---|---|
| 🔴 Important | 2 |
| 🟡 Nit | 0 |
| 🟣 Pre-existing | 0 |
| Severity | File:Line | Issue |
|---|---|---|
| 🔴 Important | src/jsc/event_loop.rs:1027-1034 |
fs.watchFile InitialStatTask is not bracketed by the off-thread fence — worker-terminate UAF on the pool thread's enqueu |
| 🔴 Important | src/runtime/webcore/s3/simple_request.rs:474-476 |
list_objects() ends the off-thread fence it never begins → outstanding_offthread underflow |
Annotations
Check failure on line 1034 in src/jsc/event_loop.rs
claude / Claude Code Review
fs.watchFile InitialStatTask is not bracketed by the off-thread fence — worker-terminate UAF on the pool thread's enqueue
Another missed sibling of the `KeepAlive::ref_` → `WorkPool::schedule` → pool-thread `enqueue_task_concurrent` shape this PR brackets: `InitialStatTask` in `src/runtime/node/node_fs_stat_watcher.rs`. `create_and_schedule` (:1160-1168) does `StatWatcher::ref_` + `WorkPool::schedule_new` with no `offthread_job_begin()`, and the pool-thread `run_owned` reaches `self.ctx.event_loop_shared().enqueue_task_concurrent(...)` (:666, `ctx: BackRef<VirtualMachine>`) after a stat syscall — so a worker termin
Check failure on line 476 in src/runtime/webcore/s3/simple_request.rs
claude / Claude Code Review
list_objects() ends the off-thread fence it never begins → outstanding_offthread underflow
`list_objects()` (client.rs:297-370) is a second construction site for `S3HttpSimpleTask` that wires the same `http_callback` (line 354) and schedules to the HTTP thread (line 370) with no `offthread_job_begin()` / `register_terminate_cancel_hook()` — but the shared callback now unconditionally calls `offthread_job_end()` here. Every `s3.list()` completion on a worker therefore `fetch_sub(1)`s an `AtomicU32` that was never incremented: it either wraps to `u32::MAX` (10s stall + VM leak on termin
Loading