Skip to content

fix: #788 — AsyncLocalStorage real context propagation - #852

Merged
TheHypnoo merged 3 commits into
mainfrom
feat/async-local-storage-context
May 16, 2026
Merged

fix: #788 — AsyncLocalStorage real context propagation#852
TheHypnoo merged 3 commits into
mainfrom
feat/async-local-storage-context

Conversation

@TheHypnoo

Copy link
Copy Markdown
Member

Summary

  • Real AsyncLocalStorage propagation across await, .then/.catch, queueMicrotask, process.nextTick, setTimeout, setImmediate and setInterval. Closes AsyncLocalStorage: real async-context tracking across await / microtasks / timers #788.
  • ALS store stacks moved from per-handle Vec into a thread-local active context (perry-runtime/src/async_context.rs); schedulers snapshot it at registration time and restore it while the callback runs.
  • exit() now restores only the affected ALS, preserving mutations made to other instances inside the callback.
  • Microtask context restoration uses a stack (not Option) and the queueMicrotask drain participates in the same setjmp trap, so a throw through a callback does not leak the callee's context.
  • Side table PROMISE_CONTEXTS is cleared on GC sweep for GC_TYPE_PROMISE payloads. A TODO notes the follow-up to inline the snapshot into Promise once PERRY_GEN_GC_EVACUATE=1 becomes default (today the evacuator does not rewrite the side-table key).
  • Shim note for node:async_hooks rewritten: ALS is no longer flagged as losing context; the remaining gap (createHook lifecycle + real asyncId) is tracked in async_hooks: real createHook lifecycle + asyncId tracking #789.

Test plan

  • cargo fmt --check
  • cargo check -p perry-hir -p perry-codegen -p perry-runtime -p perry-stdlib
  • ./run_parity_tests.sh --filter test_async_local_storage_context — PASS (new test covering await / then / catch / nextTick / timer / immediate / interval / nested run / enterWith / exit isolation / two instances)
  • ./run_parity_tests.sh --filter test_harness_async_context — only section 8 (createHook typeof: undefined) fails, as expected for async_hooks: real createHook lifecycle + asyncId tracking #789; every ALS section now passes.

TheHypnoo and others added 3 commits May 16, 2026 12:54
Propagates AsyncLocalStorage stores across await, .then/.catch,
queueMicrotask, process.nextTick, setTimeout, setImmediate and
setInterval. Store stacks moved out of per-handle Vecs into a
thread-local active context that schedulers snapshot at registration
time and restore while the callback runs.

exit() now restores only the affected ALS so mutations to other
instances inside the callback survive. Microtask context restoration
uses a stack and the queueMicrotask drain joins the same setjmp trap,
so a throw through a callback does not leak context. The promise side
table is cleared on GC sweep; a TODO notes the follow-up to inline the
snapshot into Promise once PERRY_GEN_GC_EVACUATE=1 becomes default.

The node:async_hooks shim note no longer flags ALS as lossy; the
remaining gap (createHook lifecycle + real asyncId) is tracked in #789.
@TheHypnoo
TheHypnoo merged commit 4ead3fe into main May 16, 2026
9 checks passed
@TheHypnoo
TheHypnoo deleted the feat/async-local-storage-context branch May 16, 2026 11:42
proggeramlug added a commit that referenced this pull request May 25, 2026
…guard exports (#789, #788, #1423)

The async-context core landed earlier (#852 ALS propagation, #865 async_hooks
foundation). This finishes the remaining gaps and makes it usable in release:

#789 — async_hooks lifecycle on promise/await:
  - emit_init boxed the `type` string with POINTER_TAG, so the init hook's
    `type` argument read back as `[object Object]` instead of `"PROMISE"`.
    Box it with STRING_TAG (new box_string helper).
  - the await continuation (Task::AsyncStep) didn't fire before/after or set
    the execution id, so executionAsyncId() stayed 0 across `await` and the
    after hook never fired. Bracket the step call with
    async_hooks::before/after using the result promise's async_id (captured
    as a plain value before the callback per #1663; no-op when async_hooks
    are inactive), mirroring the Task::Promise arm.

#1764 follow-up — #1762 added class-field / direct-call guard helpers in
  typed_feedback/guards.rs with no #[used] anchors, so the auto-optimize
  thin-LTO + strip build dead-stripped them, failing the link for any
  class-field program (incl. the async harness). Add typed fn-pointer
  retention anchors for the four new guards.

Verified: executionAsyncId now survives await, init reports "PROMISE", and
before/after both fire (byte-for-byte vs node). All four async tests pass
under both PERRY_NO_AUTO_OPTIMIZE and auto-optimize; removed their stale
known_failures entries.

Closes #789, #788, #1423.
proggeramlug added a commit that referenced this pull request May 25, 2026
…guard exports (#789, #788, #1423) (#1769)

The async-context core landed earlier (#852 ALS propagation, #865 async_hooks
foundation). This finishes the remaining gaps and makes it usable in release:

#789 — async_hooks lifecycle on promise/await:
  - emit_init boxed the `type` string with POINTER_TAG, so the init hook's
    `type` argument read back as `[object Object]` instead of `"PROMISE"`.
    Box it with STRING_TAG (new box_string helper).
  - the await continuation (Task::AsyncStep) didn't fire before/after or set
    the execution id, so executionAsyncId() stayed 0 across `await` and the
    after hook never fired. Bracket the step call with
    async_hooks::before/after using the result promise's async_id (captured
    as a plain value before the callback per #1663; no-op when async_hooks
    are inactive), mirroring the Task::Promise arm.

#1764 follow-up — #1762 added class-field / direct-call guard helpers in
  typed_feedback/guards.rs with no #[used] anchors, so the auto-optimize
  thin-LTO + strip build dead-stripped them, failing the link for any
  class-field program (incl. the async harness). Add typed fn-pointer
  retention anchors for the four new guards.

Verified: executionAsyncId now survives await, init reports "PROMISE", and
before/after both fire (byte-for-byte vs node). All four async tests pass
under both PERRY_NO_AUTO_OPTIMIZE and auto-optimize; removed their stale
known_failures entries.

Closes #789, #788, #1423.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AsyncLocalStorage: real async-context tracking across await / microtasks / timers

1 participant