Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.d/10952-async-resource-direct-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
`new AsyncResource(...)` and `createHook(...)` now return ordinary objects (#10926, direct half). Both used to hand JS a raw `Box::into_raw` address with no `GcHeader`, so every type probe read the bytes in front of the `Box` and `JSON.stringify(new AsyncResource("x"))` printed `""` or `[]` depending on the build, where Node prints `{}`. The `Box` is still the module's internal state. The JS value is now a `GC_TYPE_OBJECT` whose `ObjectMeta.native_state` word records the backing address, and every `js_async_resource_*` / `js_async_hook_*` entry point resolves its receiver to that backing (`resolve_async_resource_handle` / `resolve_async_hook_handle`). The resolver never does a property get: it sits on the generic property-miss path, and an earlier draft that did recursed until the stack overflowed. Class ids: `AsyncHook` takes `native_class_ids::ASYNC_HOOK` (`0xFFFF_2411`). `AsyncResource` keeps its legacy `0xFFFF_0079` as `ASYNC_RESOURCE_LEGACY`, because emitted code already bakes that id in. Both ids are in `is_native_backed_class_id`, so neither can cross a worker boundary. The `hot_diag` receiver-representation arms for both families are gone, and their fixtures now assert they are migrated. The subclass half, which links `R.prototype.[[Prototype]]` to `AsyncResource.prototype`, is held for a codegen change in `property_get.rs`.

Native callers that stored `js_async_resource_new`'s result still assumed it was the backing, and they were fixed in the same change. `set_async_resource_event_emitter` now resolves its argument. It used to drop the link without any error, so `eear.asyncResource.eventEmitter` was `undefined`. The `EventEmitterAsyncResource` subclass brand check in `node_stream_dispatch` now resolves as well. It used to reject its own hidden resource, so `emit` on a subclass threw `Cannot read private member`. `EventEmitterHandle.async_resource_handle` now holds a movable object, so both copies of the events scanner visit it: the in-tree `stdlib:events` one and the one in `perry-ext-events`, which is the archive the compiler actually routes `node:events` to. Without the visit, the first collection left `eear.asyncId` reading `0` and ran listeners outside the resource's scope. `js_event_emitter_async_resource_subclass_init` roots the resource object across the hidden-key allocation that comes before storing it. Adds `test-files/test_gap_10952_eventemitter_async_resource_object.ts`.

`async_hooks.rs` would have gone over the 2000-line cap, so its argument-conversion and error-rendering helpers move to `async_hooks/arg_values.rs`. That block has no raw-handle sites, so no ratchet entry moves. The test-only `TEST_FORCE_RESOLVE_GC` hook moved from the resolver into `js_async_resource_run_in_async_scope`, and its root-holders exemption is deleted.
9 changes: 9 additions & 0 deletions crates/perry-ext-events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ pub struct EventEmitterHandle {
max_listeners: f64,
capture_rejections: bool,
domain_handle: Option<Handle>,
/// The public AsyncResource handle OBJECT (#10926): movable, so the GC
/// scanner visits this slot.
async_resource_handle: i64,
}

Expand Down Expand Up @@ -541,6 +543,13 @@ fn scan_events_roots(visitor: &mut EventsRootVisitor) {
}
}
}
// #10926: `js_async_resource_new` returns an ordinary, movable handle
// object now (it used to be a never-freed `Box`), and this slot is its
// only native-side holder -- the same visit the in-tree
// `perry-stdlib` events scanner makes.
if is_heap_pointer_candidate(emitter.async_resource_handle) {
visitor.visit_i64_slot(&mut emitter.async_resource_handle);
}
for pending in emitter.pending_once_promises.values_mut() {
for p in pending.iter_mut() {
visitor.visit_raw_mut_ptr_slot(&mut p.promise);
Expand Down
Loading
Loading