next.js
edeb5739 - Pin tasks across restore waiter handoff (#98728)

Commit
54 days ago
Pin tasks across restore waiter handoff (#98728) ### What? Keep a task alive across the restore-waiter handoff: from the moment a caller observes another thread restoring a category until that caller acquires the restored task for use. This is the first open layer in stack #98709. The construction and restore-owner races have already landed in `canary` through #98688 and #98700. ### Why? The `restoring` bits elect exactly one thread to perform backing-storage I/O. A waiting caller observes that ownership under the task lock, drops the lock, and then holds only a `TaskId` while waiting for notification. That temporary in-session reference is not represented by a parent or aggregation edge, so GC may otherwise collect the task after the restorer clears its bit but before the waiter acquires a guard. Eviction is different: it is safe for eviction to clear the completed restore in this window as long as the waiter does not mistake that state for an I/O failure. The waiter can claim the now-unowned category and restore it again. Batch preparation has the same task-id-outside-the-graph interval between classification and callback. Pair access needs both task IDs kept alive until both guards can be acquired together. ### How? Each waiting caller increments the existing `transient_ref_count` before dropping the task lock and decrements exactly one ref under the guard at its actual use boundary. This uses the GC-root mechanism and invariant checks that already protect construction, transient-parent, and detached-handle references. The transient ref does not block value eviction. If a waiter observes a category that is neither restored nor currently restoring, it keeps its GC pin, claims the category, and performs another restore. A replacement restorer retains its acquired guard through notification and returns that same still-pinned guard to the caller. Successful pin release belongs entirely to the caller: - single-task access releases after receiving the restored guard; - pair access releases after acquiring both guards together; - batch preparation releases immediately before passing the guard to its callback. The helper decrements the ref itself only on `Err`, where no guard is returned. The already-restored fast path remains and avoids listener registration. This removes the dedicated handoff field and eviction blocker. It also keeps `gc_maybe_collectible()` and `gc_debug_assert_root_held_by_transient_pin()` aligned on the same existing transient-ref predicate. ### Verification - Latest `origin/canary` (`389f164d`) in a detached worktree - `cargo test -p turbo-tasks-backend --lib` (102 passed) - `cargo test -p turbo-tasks-backend --test eviction --test gc_resurrection --test gc_collection --test gc_interrupt --test gc_stress` (19 passed) - Eviction-permitting stress: 0 failures across 1,200 runs using 8 parallel workers - Sequential stress: 0 failures across 150 runs - `cargo fmt -- --check` - `cargo clippy -p turbo-tasks-backend --tests -- -D warnings` Followed by #98729. <!-- NEXT_JS_LLM --> <!-- fleet 6aa6acec-6286-4b03-86f4-13f4c7e2b2fa --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Author
Parents
Loading