Skip to content

Commit cf2a508

Browse files
proggeramlugRalph Küpper
andauthored
fix(windows): scanned WinUI callback keys; complete Node async_hooks lifecycle parity (#8797)
Lands #8764 and #8671. #8764 keeps WinUI callbacks behind scanned keys, addressing #8734: the callback pointers that were copied out of the GC-scanned tables into Reactor element-tree closures, where no scanner could reach or rewrite them. This is the Windows-side fix, authored on a Windows host. #8671 completes Node async_hooks lifecycle parity. Both were showing pr-gate red before #8791 landed, because main itself was failing `cargo-test` on a Web Streams test. No version bump. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
1 parent 726f6e9 commit cf2a508

141 files changed

Lines changed: 5243 additions & 1750 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
### Fixed
2+
3+
- Complete the `node:async_hooks` parity tracker across all 194 fixtures:
4+
hook mutation and lifecycle ordering, Promise/resource identity and trigger
5+
chains, `AsyncResource` and `EventEmitterAsyncResource` subclasses,
6+
`AsyncLocalStorage` propagation, and provider lifecycles for timers, files,
7+
DNS, crypto, zlib, processes, signals, workers, streams, net, HTTP(S), TLS,
8+
readline, event iterators, ESM, fetch, and UDP now match Node.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
3+
- Fixed WinUI widget, application-exit, and timer callbacks becoming stale after an evacuating garbage collection. Windows Reactor closures now retain stable widget/slot keys and resolve callbacks from GC-scanned storage immediately before invocation; timer callbacks likewise remain in a scanned key-indexed table instead of being copied into opaque `DispatcherTimer` closures.

crates/perry-api-manifest/src/entries/part_4.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,7 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
838838
method("http", "ref", true, Some("HttpServer")),
839839
method("http", "unref", true, Some("HttpServer")),
840840
method("http", "on", true, Some("IncomingMessage")),
841+
method("http", "once", true, Some("IncomingMessage")),
841842
method("http", "addListener", true, Some("IncomingMessage")),
842843
method("http", "pause", true, Some("IncomingMessage")),
843844
method("http", "resume", true, Some("IncomingMessage")),
@@ -856,6 +857,7 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
856857
// method, so the manifest-consistency drift guard requires a row
857858
// here even though the test collapses class_filter variants.
858859
method("http", "setTimeout", true, Some("ClientRequest")),
860+
method("http", "once", true, Some("ClientRequest")),
859861
method("http", "listenerCount", true, Some("ClientRequest")),
860862
method("http", "setHeader", true, Some("ClientRequest")),
861863
method("http", "getHeader", true, Some("ClientRequest")),

crates/perry-codegen/src/expr/calls/crypto_misc.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,18 @@ pub(crate) fn arm_crypto_prime(
271271
unreachable!()
272272
};
273273
let first_box = lower_expr(ctx, &args[0])?;
274-
let options_box = if args.len() >= 2 {
274+
let is_async = matches!(property, "generatePrime" | "checkPrime");
275+
// The callback forms are `(value, callback)` or
276+
// `(value, options, callback)`. Treating the second argument as options
277+
// unconditionally accidentally routed the common two-argument form to
278+
// the synchronous implementation and returned the generated value.
279+
let options_box = if args.len() >= 2 && (!is_async || args.len() >= 3) {
275280
lower_expr(ctx, &args[1])?
276281
} else {
277282
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
278283
};
279-
let callback_box = if matches!(property, "generatePrime" | "checkPrime") && args.len() >= 3 {
280-
Some(lower_expr(ctx, &args[2])?)
284+
let callback_box = if is_async && args.len() >= 2 {
285+
Some(lower_expr(ctx, &args[args.len().min(3) - 1])?)
281286
} else {
282287
None
283288
};

crates/perry-codegen/src/expr/env_clones.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
193193
let blk = ctx.block();
194194
let raw = blk.call(
195195
I64,
196-
"js_net_create_server",
196+
"js_ext_net_create_server",
197197
&[(I64, &options_i64), (I64, &listener_i64)],
198198
);
199199
Ok(nanbox_pointer_inline(blk, &raw))

crates/perry-codegen/src/expr/instance_misc1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
483483
// Promise values are raw promise allocations, not ObjectHeader
484484
// instances with a class_id field.
485485
"Promise" => 0xFFFF0027u32,
486+
"AsyncLocalStorage" => 0xFFFF0078u32,
487+
"AsyncResource" => 0xFFFF0079u32,
486488
// WHATWG fetch types. Like Blob/streams these are pointer-tagged
487489
// small-int handles; the runtime resolves them via the stdlib
488490
// fetch kind-probe (`res instanceof Response`, etc.).

crates/perry-codegen/src/expr/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ pub(crate) use write_barrier::{
132132
emit_root_heap_word_store_on_block, emit_root_nanbox_store_on_block, emit_write_barrier,
133133
emit_write_barrier_slot_generation_tested, emit_write_barrier_slot_on_block,
134134
emit_write_barrier_slot_value_and_generation_tested, lower_array_super_init,
135-
lower_event_emitter_subclass_init, lower_node_stream_super_init, lower_stream_super_init,
135+
lower_event_emitter_async_resource_subclass_init, lower_event_emitter_subclass_init,
136+
lower_node_stream_super_init, lower_stream_super_init,
136137
};
137138

138139
// Issue #1098 phase 3: the `FnCtx` definition stays in this trunk, but its

crates/perry-codegen/src/expr/property_get.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
13761376
if receiver_class_is_proven && is_net_native_method_value(&class_name, property) {
13771377
return lower_class_method_bind(ctx, object, property);
13781378
}
1379+
if receiver_class_is_proven
1380+
&& class_name == "AsyncResource"
1381+
&& matches!(
1382+
property.as_str(),
1383+
"asyncId" | "triggerAsyncId" | "emitDestroy" | "runInAsyncScope" | "bind"
1384+
)
1385+
{
1386+
return lower_runtime_property_get_by_name(ctx, object, property);
1387+
}
13791388
if class_has_computed_runtime_members(ctx, &class_name) {
13801389
return lower_runtime_property_get_by_name(ctx, object, property);
13811390
}

crates/perry-codegen/src/expr/this_super_call.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use crate::nanbox::{double_literal, POINTER_MASK_I64};
1212
use crate::types::{DOUBLE, I1, I32, I64, PTR};
1313

1414
use super::{
15-
lower_array_super_init, lower_event_emitter_subclass_init, lower_expr,
16-
lower_node_stream_super_init, lower_stream_super_init, nanbox_pointer_inline, FnCtx,
15+
lower_array_super_init, lower_event_emitter_async_resource_subclass_init,
16+
lower_event_emitter_subclass_init, lower_expr, lower_node_stream_super_init,
17+
lower_stream_super_init, nanbox_pointer_inline, FnCtx,
1718
};
1819

1920
/// Enter one derived constructor's `super()` binding scope.
@@ -826,6 +827,83 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
826827
)?;
827828
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
828829
}
830+
if parent_name.as_str() == "EventEmitterAsyncResource" {
831+
let mut lowered = Vec::with_capacity(super_args.len());
832+
for arg in super_args {
833+
lowered.push(lower_expr(ctx, arg)?);
834+
}
835+
let options = lowered.first().cloned().unwrap_or_else(|| {
836+
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
837+
});
838+
let this_box = match ctx.this_stack.last().cloned() {
839+
Some(slot) => ctx.block().load(DOUBLE, &slot),
840+
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
841+
};
842+
lower_event_emitter_async_resource_subclass_init(ctx, &this_box, &options);
843+
bind_derived_this_after_super(ctx);
844+
let current_class_name =
845+
ctx.class_stack.last().cloned().unwrap_or_default();
846+
crate::lower_call::apply_field_initializers_recursive(
847+
ctx,
848+
&current_class_name,
849+
crate::lower_call::FieldInitMode::SelfOnly,
850+
)?;
851+
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
852+
}
853+
if parent_name.as_str() == "AsyncLocalStorage" {
854+
for arg in super_args {
855+
let _ = lower_expr(ctx, arg)?;
856+
}
857+
let this_box = match ctx.this_stack.last().cloned() {
858+
Some(slot) => ctx.block().load(DOUBLE, &slot),
859+
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
860+
};
861+
ctx.block().call(
862+
DOUBLE,
863+
"js_async_local_storage_subclass_init",
864+
&[(DOUBLE, &this_box)],
865+
);
866+
bind_derived_this_after_super(ctx);
867+
let current_class_name =
868+
ctx.class_stack.last().cloned().unwrap_or_default();
869+
crate::lower_call::apply_field_initializers_recursive(
870+
ctx,
871+
&current_class_name,
872+
crate::lower_call::FieldInitMode::SelfOnly,
873+
)?;
874+
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
875+
}
876+
if parent_name.as_str() == "AsyncResource" {
877+
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
878+
let mut lowered = Vec::with_capacity(super_args.len());
879+
for arg in super_args {
880+
lowered.push(lower_expr(ctx, arg)?);
881+
}
882+
let type_value = lowered.first().cloned().unwrap_or_else(|| undef.clone());
883+
let options = lowered.get(1).cloned().unwrap_or_else(|| undef.clone());
884+
let this_box = match ctx.this_stack.last().cloned() {
885+
Some(slot) => ctx.block().load(DOUBLE, &slot),
886+
None => undef,
887+
};
888+
ctx.block().call(
889+
DOUBLE,
890+
"js_async_resource_subclass_init",
891+
&[
892+
(DOUBLE, &this_box),
893+
(DOUBLE, &type_value),
894+
(DOUBLE, &options),
895+
],
896+
);
897+
bind_derived_this_after_super(ctx);
898+
let current_class_name =
899+
ctx.class_stack.last().cloned().unwrap_or_default();
900+
crate::lower_call::apply_field_initializers_recursive(
901+
ctx,
902+
&current_class_name,
903+
crate::lower_call::FieldInitMode::SelfOnly,
904+
)?;
905+
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
906+
}
829907
// `class X extends Request` / `extends Response`:
830908
// `super(input, init)` allocates the underlying native
831909
// Web-Fetch handle and stashes its id on `this` under

crates/perry-codegen/src/expr/write_barrier.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,3 +1081,15 @@ pub(crate) fn lower_event_emitter_subclass_init(ctx: &mut FnCtx<'_>, this_box: &
10811081
&[(DOUBLE, this_box)],
10821082
);
10831083
}
1084+
1085+
pub(crate) fn lower_event_emitter_async_resource_subclass_init(
1086+
ctx: &mut FnCtx<'_>,
1087+
this_box: &str,
1088+
options_box: &str,
1089+
) {
1090+
ctx.block().call(
1091+
DOUBLE,
1092+
"js_event_emitter_async_resource_subclass_init",
1093+
&[(DOUBLE, this_box), (DOUBLE, options_box)],
1094+
);
1095+
}

0 commit comments

Comments
 (0)