Skip to content

re-land #8671: async_hooks lifecycle parity (defer crypto completions) - #8814

Merged
proggeramlug merged 1 commit into
mainfrom
fix/8671-pbkdf2-callback
Aug 25, 2026
Merged

re-land #8671: async_hooks lifecycle parity (defer crypto completions)#8814
proggeramlug merged 1 commit into
mainfrom
fix/8671-pbkdf2-callback

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Re-lands #8671 (async_hooks lifecycle parity) with the one test that blocked it corrected.

I mischaracterised this PR when I reverted it — correcting that here

I backed #8671 out in #8798 and reported it as "breaks crypto.pbkdf2's callback — a real caller would hang." That was wrong. #8671 made pbkdf2 more Node-accurate, and a stale unit test caught the behaviour change.

Node runs crypto.pbkdf2 on the libuv threadpool, so its (err, key) callback is never invoked synchronously. #8671 correctly moved native crypto completions from a synchronous js_closure_call2 to schedule_native_callback(..., "PBKDF2REQUEST"), so the callback executes inside its own async resource and async_hooks observes init → before → after → destroy around it — which is what the PR's own parity fixture test-parity/node-suite/async_hooks/hooks/provider-pbkdf2-lifecycle.ts requires, and what the async randomBytes form has done since #6430.

native_dispatch_pbkdf2_value_form_fires_callback still asserted the old synchronous delivery, i.e. it encoded non-Node behaviour.

I also stated on this PR that the test "passes when filtered in isolation, and fails only in the full suite", and used that to argue for leaked suite-global state. That was an unverified inference — I had only checked isolation at commits that did not contain #8671, then carried the claim over. The failure is deterministic and order-independent. Apologies for sending anyone down that path.

The revert itself still stands as the right immediate action — main was red and attribution was bisected — but the diagnosis attached to it was not.

The fix

native_dispatch_pbkdf2_value_form_fires_callback now asserts the delivery contract its sibling randomBytes test already encodes. It is strictly stronger than before — all three original assertions retained, two added:

  1. the callback must not fire synchronously;
  2. js_callback_timer_has_pending() != 0 — the scheduled completion is a pending, ref'd event-loop handle. This is the direct answer to my "a caller would hang" worry: it is the same predicate crates/perry-codegen/src/codegen/entry.rs:1158 emits into the compiled program's event loop, so a real util.promisify(crypto.pbkdf2) caller cannot exit before the callback runs;
  3. after one js_callback_timer_tick(), the callback fires with a null error and a Buffer pointer.

Re-application and conflict resolution

A plain merge of the PR branch into current main is a no-op — the revert is the newer change on main, so the 3-way merge discards the re-application. git revert-ing the revert squash f46932a78 was also wrong, since that squash re-landed #8764. #8671's own diff was re-applied against 974c57fca.

One conflict, in crates/perry-stdlib/src/fetch/mod.rs: main's #8805 js_promise_newjs_promise_new_cross_thread vs #8671's run_provider_completion refactor. Resolved keeping #8671's structure with main's cross-thread call — verified 15 js_promise_new_cross_thread call sites on both main and this branch, and zero remaining plain js_promise_new(.

Also audited: #8671 drops an is_valid_obj_ptr guard in array/subclass.rs::dense_layout_for_value, relying on try_read_gc_header(...)? instead. That is safe de-duplication, not a lost check — try_read_gc_header calls is_plausible_heap_addr first (rejecting null and implausible addresses) and additionally rejects small-buffer slab addresses, so it is strictly stronger than the guard removed.

Validation (verified independently, not taken on report)

  • all 30 lint-job gates pass
  • perry-stdlib --lib full suite: 120 passed; 0 failed (was 119 passed; 1 failed)
  • the same test filtered alone: 1 passed; 0 failed
  • perry-runtime --lib: 2689 passed; 0 failed
  • perry-codegen --lib: 1249 passed; 0 failed
  • no version bump; Cargo.toml and CLAUDE.md untouched

Summary by CodeRabbit

  • New Features
    • Added broad node:async_hooks compatibility, including AsyncResource, AsyncLocalStorage, and EventEmitterAsyncResource.
    • Added events.on() async iteration and events.addAbortListener().
    • Added once() support for HTTP request and incoming-message objects.
    • Added support for dynamic imports from JavaScript data: URLs.
  • Bug Fixes
    • Improved callback timing and async context tracking across crypto, filesystem, networking, streams, DNS, workers, and promises.
    • Improved readline buffering and replay of pending input and close events.
  • Documentation
    • Updated API reference and changelog coverage.

…tions

Re-lands #8671 with the pbkdf2 regression that caused its revert fixed.

#8671 routed every native Node-style crypto completion callback through
`timer::schedule_native_callback`, which enqueues the callback on the check
phase inside its own async-hooks provider resource (`PBKDF2REQUEST`,
`SCRYPTREQUEST`, ...) so `init`/`before`/`after`/`destroy` are observed
around it. That is what Node does — `crypto.pbkdf2` completes on the libuv
threadpool and never calls back synchronously — and it is what the async
`randomBytes` form has done since #6430.

`crypto::random::tests::native_dispatch_pbkdf2_value_form_fires_callback`
still pinned the previous, non-Node behaviour: it asserted the callback had
fired the instant `js_crypto_native_dispatch` returned. It failed
deterministically (also in isolation — this was not suite-global state).

Update the test to the delivery contract the sibling `randomBytes` test
already encodes: the callback must NOT fire synchronously, the scheduled
completion must be a pending ref'd event-loop handle (this is the same
`js_callback_timer_has_pending` predicate the codegen event loop emits in
`codegen/entry.rs`, so a real `util.promisify(crypto.pbkdf2)` caller cannot
exit before it runs), and after one `js_callback_timer_tick` the callback
must fire with a null error and a Buffer pointer.
@proggeramlug
proggeramlug merged commit 8f026e5 into main Aug 25, 2026
13 of 16 checks passed
@proggeramlug
proggeramlug deleted the fix/8671-pbkdf2-callback branch August 25, 2026 10:10
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19566205-6391-41e4-b02c-680a21c04f9c

📥 Commits

Reviewing files that changed from the base of the PR and between e043aa2 and 98eb2fa.

📒 Files selected for processing (138)
  • changelog.d/8671-async-hooks-parity.md
  • crates/perry-api-manifest/src/entries/part_4.rs
  • crates/perry-codegen/src/expr/calls/crypto_misc.rs
  • crates/perry-codegen/src/expr/env_clones.rs
  • crates/perry-codegen/src/expr/instance_misc1.rs
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/expr/write_barrier.rs
  • crates/perry-codegen/src/ext_registry.rs
  • crates/perry-codegen/src/lower_call/builtin.rs
  • crates/perry-codegen/src/lower_call/native_table/http_client.rs
  • crates/perry-codegen/src/lower_call/native_table/http_server.rs
  • crates/perry-codegen/src/lower_call/native_table/net_events.rs
  • crates/perry-codegen/src/lower_call/new_helpers.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/language_core.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/streams_events.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/third_party.rs
  • crates/perry-ext-events/src/lib.rs
  • crates/perry-ext-events/src/module_iterators.rs
  • crates/perry-ext-events/src/module_on.rs
  • crates/perry-ext-events/src/target_helpers.rs
  • crates/perry-ext-events/src/tests.rs
  • crates/perry-ext-http/src/lib.rs
  • crates/perry-ext-http/src/pending_dispatch.rs
  • crates/perry-ext-http/src/server/server.rs
  • crates/perry-ext-http/src/server/server/deferred_events.rs
  • crates/perry-ext-http/src/tests.rs
  • crates/perry-ext-net/src/adopt.rs
  • crates/perry-ext-net/src/dispatch.rs
  • crates/perry-ext-net/src/gc_roots.rs
  • crates/perry-ext-net/src/handle_exports.rs
  • crates/perry-ext-net/src/ipc.rs
  • crates/perry-ext-net/src/lib.rs
  • crates/perry-ext-net/src/lifecycle.rs
  • crates/perry-ext-net/src/provider_lifecycle.rs
  • crates/perry-ext-net/src/server_state.rs
  • crates/perry-ext-net/src/task_spawn.rs
  • crates/perry-ext-net/src/tests.rs
  • crates/perry-ext-net/src/tls.rs
  • crates/perry-ext-zlib/src/stream.rs
  • crates/perry-hir/src/lower/stmt.rs
  • crates/perry-hir/src/lower_decl/body_stmt.rs
  • crates/perry-runtime/src/array/subclass.rs
  • crates/perry-runtime/src/async_context.rs
  • crates/perry-runtime/src/async_hooks.rs
  • crates/perry-runtime/src/async_hooks/test_support.rs
  • crates/perry-runtime/src/child_process/emitter.rs
  • crates/perry-runtime/src/child_process/reactor.rs
  • crates/perry-runtime/src/child_process/reactor/windows_kill_tests.rs
  • crates/perry-runtime/src/closure/dispatch/value_call.rs
  • crates/perry-runtime/src/dgram.rs
  • crates/perry-runtime/src/dgram/listeners.rs
  • crates/perry-runtime/src/dgram/ops.rs
  • crates/perry-runtime/src/dns.rs
  • crates/perry-runtime/src/dns/ffi.rs
  • crates/perry-runtime/src/fs/callbacks.rs
  • crates/perry-runtime/src/fs/dir_glob_watch/watch.rs
  • crates/perry-runtime/src/fs/filehandle.rs
  • crates/perry-runtime/src/gc/roots.rs
  • crates/perry-runtime/src/module_require.rs
  • crates/perry-runtime/src/node_stream.rs
  • crates/perry-runtime/src/node_stream_constructors.rs
  • crates/perry-runtime/src/node_stream_constructors/builders.rs
  • crates/perry-runtime/src/node_stream_constructors/pipeline.rs
  • crates/perry-runtime/src/node_stream_dispatch.rs
  • crates/perry-runtime/src/node_submodules/blob.rs
  • crates/perry-runtime/src/node_submodules/fs_promises.rs
  • crates/perry-runtime/src/node_vm.rs
  • crates/perry-runtime/src/object/class_handles.rs
  • crates/perry-runtime/src/object/class_registry.rs
  • crates/perry-runtime/src/object/class_registry/parent_static.rs
  • crates/perry-runtime/src/object/class_registry/parent_static/unstamped_tests.rs
  • crates/perry-runtime/src/object/class_registry/prototype_objects.rs
  • crates/perry-runtime/src/object/descriptors.rs
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_async.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs
  • crates/perry-runtime/src/object/field_get_set/ic_miss.rs
  • crates/perry-runtime/src/object/instanceof.rs
  • crates/perry-runtime/src/object/native_module.rs
  • crates/perry-runtime/src/object/native_module/async_hooks_exports.rs
  • crates/perry-runtime/src/object/native_module_dispatch/dispatch_a_c.rs
  • crates/perry-runtime/src/object/nm_namespace_hooks.rs
  • crates/perry-runtime/src/object/reflect_support.rs
  • crates/perry-runtime/src/object/tests.rs
  • crates/perry-runtime/src/object/to_string_tag.rs
  • crates/perry-runtime/src/os/signal.rs
  • crates/perry-runtime/src/promise/assimilate.rs
  • crates/perry-runtime/src/promise/async_step.rs
  • crates/perry-runtime/src/promise/microtasks.rs
  • crates/perry-runtime/src/promise/mod.rs
  • crates/perry-runtime/src/promise/scanners.rs
  • crates/perry-runtime/src/promise/then.rs
  • crates/perry-runtime/src/proxy.rs
  • crates/perry-runtime/src/proxy/reflect.rs
  • crates/perry-runtime/src/symbol.rs
  • crates/perry-runtime/src/symbol/get.rs
  • crates/perry-runtime/src/symbol/properties.rs
  • crates/perry-runtime/src/timer.rs
  • crates/perry-runtime/src/value/addr_class.rs
  • crates/perry-stdlib/Cargo.toml
  • crates/perry-stdlib/src/async_local_storage.rs
  • crates/perry-stdlib/src/common/dispatch.rs
  • crates/perry-stdlib/src/common/dispatch/emitter_als.rs
  • crates/perry-stdlib/src/common/dispatch/init.rs
  • crates/perry-stdlib/src/common/dispatch/method_dispatch.rs
  • crates/perry-stdlib/src/common/dispatch/property_dispatch.rs
  • crates/perry-stdlib/src/common/dispatch_http.rs
  • crates/perry-stdlib/src/crypto/kdf.rs
  • crates/perry-stdlib/src/crypto/keys.rs
  • crates/perry-stdlib/src/crypto/prime.rs
  • crates/perry-stdlib/src/crypto/random.rs
  • crates/perry-stdlib/src/events/constructors.rs
  • crates/perry-stdlib/src/fetch/mod.rs
  • crates/perry-stdlib/src/readline/mod.rs
  • crates/perry-stdlib/src/readline/test_support.rs
  • crates/perry-stdlib/src/tls.rs
  • crates/perry-stdlib/src/tls/event_pump.rs
  • crates/perry-stdlib/src/webcrypto/aes.rs
  • crates/perry-stdlib/src/webcrypto/digest.rs
  • crates/perry-stdlib/src/webcrypto/hmac.rs
  • crates/perry-stdlib/src/webcrypto/util.rs
  • crates/perry-stdlib/src/worker_threads.rs
  • crates/perry-stdlib/src/worker_threads/worker_pump.rs
  • crates/perry-stdlib/src/zlib.rs
  • crates/perry/src/commands/compile/build_cache.rs
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/optimized_libs/driver.rs
  • crates/perry/src/commands/compile/optimized_libs/freshness.rs
  • crates/perry/src/commands/compile/optimized_libs/tests.rs
  • crates/perry/src/commands/compile/types.rs
  • docs/src/api/reference.md
  • scripts/gc_runtime_root_holders.json
  • scripts/raw_handle_debt_files.txt
  • scripts/string_payload_access_baseline.txt
  • scripts/thread_local_cold_allowlist.json

📝 Walkthrough

Walkthrough

This change expands async-hooks parity across runtime resources, promises, callbacks, networking, HTTP, events, filesystem APIs, workers, zlib, crypto, and WebCrypto. It also adds EventEmitterAsyncResource support, dynamic-import handling, compiler wiring, and API manifest updates.

Changes

Async-hooks runtime and resource lifecycle

Layer / File(s) Summary
Core async-hooks state and context
crates/perry-runtime/src/async_hooks.rs, async_context.rs, promise/*, timer.rs
Adds provider lifecycle APIs, resource scopes, generation-aware context storage, promise parentage, callback scheduling, and GC handling.
Async-resource object and property dispatch
crates/perry-runtime/src/object/*, crates/perry-stdlib/src/async_local_storage.rs, common/dispatch/*
Adds native and subclass receiver resolution, method/property dispatch, branding, symbol expandos, and constructor enforcement.
Native extension resource tracking
crates/perry-ext-http/*, perry-ext-net/*, perry-ext-zlib/*, perry-ext-events/*, perry-stdlib/src/worker_threads*
Tracks async resources through HTTP, network, zlib, events, workers, child processes, datagrams, DNS, filesystem watchers, and signals.
EventEmitterAsyncResource and events APIs
crates/perry-ext-events/*, crates/perry-runtime/src/node_stream*
Adds EventEmitter async-resource construction, subclass methods, provider dispatch, events.on() async iterators, abort listeners, and event emission scopes.
Callback scheduling and module semantics
crates/perry-runtime/src/{dns,fs,timer,module_require}.rs, crates/perry-stdlib/src/{crypto,webcrypto,fetch}/*
Defers native completions through provider-aware timer queues and adds data-URL dynamic import evaluation.
Compiler, FFI, and API wiring
crates/perry-codegen/*, crates/perry-hir/*, crates/perry-stdlib/Cargo.toml
Wires async-resource subclasses, DNS resolvers, external network symbols, TLS routing, HTTP once, and native FFI declarations.
Readline, TLS, and supporting behavior
crates/perry-stdlib/src/readline/*, tls/*, node_stream_constructors/pipeline.rs
Preserves buffered readline events, adds TLS event-pump exports, and handles default stream completion callbacks.
Build and validation artifacts
crates/perry/src/commands/compile/*, docs/src/api/reference.md, changelog.d/*, scripts/*
Updates feature detection, cache keys, generated API entries, changelog content, and static runtime inventories.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: thehypnoo

Sequence Diagram(s)

sequenceDiagram
  participant NativeAPI
  participant ProviderRuntime
  participant EventLoop
  participant JavaScriptCallback
  NativeAPI->>ProviderRuntime: initialize provider resource
  NativeAPI->>EventLoop: schedule completion
  EventLoop->>ProviderRuntime: enter resource scope
  ProviderRuntime->>JavaScriptCallback: before and callback
  JavaScriptCallback->>ProviderRuntime: after completion
  ProviderRuntime->>ProviderRuntime: destroy provider resource
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/8671-pbkdf2-callback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

1 participant