Skip to content
Closed
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
3 changes: 3 additions & 0 deletions changelog.d/8815-async-hooks-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Completed Node `async_hooks` lifecycle support across async resources, event emitters, HTTP, sockets, workers, zlib, DNS, and WebCrypto. Provider scopes now restore execution and `AsyncLocalStorage` state when hooks or callbacks throw, deferred destroy hooks run at the correct lifecycle boundary, and allocation-sensitive values remain rooted across moving garbage collections.
166 changes: 118 additions & 48 deletions crates/perry-codegen/src/expr/this_super_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use perry_hir::Expr;

use crate::lower_call::{bind_inline_constructor_params, restore_inline_constructor_scope};
use crate::nanbox::{double_literal, POINTER_MASK_I64};
use crate::rooting::{self, Repr};
use crate::types::{DOUBLE, I1, I32, I64, PTR};

use super::{
Expand Down Expand Up @@ -257,6 +258,76 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
};
let async_parent = ctx
.classes
.get(&current_class_name)
.filter(|class| class.extends_expr.is_none() && !class.heritage_lexically_shadowed)
.and_then(|class| class.extends_name.clone())
.filter(|parent| !ctx.classes.contains_key(parent.as_str()));
if matches!(
async_parent.as_deref(),
Some("EventEmitterAsyncResource" | "AsyncLocalStorage" | "AsyncResource")
) {
Comment thread
proggeramlug marked this conversation as resolved.
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let zero_idx = "0".to_string();
let one_idx = "1".to_string();
rooting::with_rooted_group(ctx, 4, |ctx, group| {
let this_root = group.adopt_emitted(ctx, Repr::Boxed, &this_box, true);
let arr_root = group.adopt_emitted(ctx, Repr::Ptr, &arr, true);
let arr = group.reread_emitted(ctx, arr_root);
let first = ctx.block().call(
DOUBLE,
"js_array_get_f64",
&[(I64, &arr), (I32, &zero_idx)],
);
let first_root = group.adopt_emitted(ctx, Repr::Boxed, &first, true);
let arr = group.reread_emitted(ctx, arr_root);
let second = ctx.block().call(
DOUBLE,
"js_array_get_f64",
&[(I64, &arr), (I32, &one_idx)],
);
let second_root = group.adopt_emitted(ctx, Repr::Boxed, &second, true);
let this_box = group.reread_emitted(ctx, this_root);
match async_parent.as_deref() {
Some("EventEmitterAsyncResource") => {
let options = group.reread_emitted(ctx, first_root);
lower_event_emitter_async_resource_subclass_init(
ctx, &this_box, &options,
);
}
Some("AsyncLocalStorage") => {
ctx.block().call(
DOUBLE,
"js_async_local_storage_subclass_init",
&[(DOUBLE, &this_box)],
);
}
Some("AsyncResource") => {
let type_value = group.reread_emitted(ctx, first_root);
let options = group.reread_emitted(ctx, second_root);
ctx.block().call(
DOUBLE,
"js_async_resource_subclass_init",
&[
(DOUBLE, &this_box),
(DOUBLE, &type_value),
(DOUBLE, &options),
],
);
}
_ => unreachable!(),
}
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(undef.clone())
})?;
return Ok(undef);
}
// `class X extends Map | Set` with a spread super (`super(...args)`,
// e.g. NestJS's `ModulesContainer`'s `super(...arguments)`) — install
// the hidden collection backing from the (possibly spread) args
Expand Down Expand Up @@ -828,27 +899,28 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
}
if parent_name.as_str() == "EventEmitterAsyncResource" {
let mut lowered = Vec::with_capacity(super_args.len());
for arg in super_args {
lowered.push(lower_expr(ctx, arg)?);
}
let options = lowered.first().cloned().unwrap_or_else(|| {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
let operands: Vec<_> = super_args.iter().collect();
return rooting::with_operands_rooted(ctx, &operands, |ctx, lowered| {
let options = lowered.first().cloned().unwrap_or_else(|| {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
});
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
}
};
lower_event_emitter_async_resource_subclass_init(
ctx, &this_box, &options,
);
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)))
});
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
};
lower_event_emitter_async_resource_subclass_init(ctx, &this_box, &options);
bind_derived_this_after_super(ctx);
let current_class_name =
ctx.class_stack.last().cloned().unwrap_or_default();
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
}
if parent_name.as_str() == "AsyncLocalStorage" {
for arg in super_args {
Expand All @@ -875,34 +947,32 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
}
if parent_name.as_str() == "AsyncResource" {
let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
let mut lowered = Vec::with_capacity(super_args.len());
for arg in super_args {
lowered.push(lower_expr(ctx, arg)?);
}
let type_value = lowered.first().cloned().unwrap_or_else(|| undef.clone());
let options = lowered.get(1).cloned().unwrap_or_else(|| undef.clone());
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => undef,
};
ctx.block().call(
DOUBLE,
"js_async_resource_subclass_init",
&[
(DOUBLE, &this_box),
(DOUBLE, &type_value),
(DOUBLE, &options),
],
);
bind_derived_this_after_super(ctx);
let current_class_name =
ctx.class_stack.last().cloned().unwrap_or_default();
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
return Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)));
let operands: Vec<_> = super_args.iter().collect();
return rooting::with_operands_rooted(ctx, &operands, |ctx, lowered| {
let type_value =
lowered.first().cloned().unwrap_or_else(|| undef.clone());
let options = lowered.get(1).cloned().unwrap_or_else(|| undef.clone());
let this_box = match ctx.this_stack.last().cloned() {
Some(slot) => ctx.block().load(DOUBLE, &slot),
None => undef.clone(),
};
ctx.block().call(
DOUBLE,
"js_async_resource_subclass_init",
&[
(DOUBLE, &this_box),
(DOUBLE, &type_value),
(DOUBLE, &options),
],
);
bind_derived_this_after_super(ctx);
crate::lower_call::apply_field_initializers_recursive(
ctx,
&current_class_name,
crate::lower_call::FieldInitMode::SelfOnly,
)?;
Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)))
});
}
// `class X extends Request` / `extends Response`:
// `super(input, init)` allocates the underlying native
Expand Down
6 changes: 6 additions & 0 deletions crates/perry-codegen/src/ext_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
("js_https_request", OwnerKind::WellKnown("http")),
("js_https_get", OwnerKind::WellKnown("http")),
("js_http_on", OwnerKind::WellKnown("http")),
("js_http_once", OwnerKind::WellKnown("http")),
("js_http_set_header", OwnerKind::WellKnown("http")),
("js_http_set_timeout", OwnerKind::WellKnown("http")),
("js_http_set_timeout_full", OwnerKind::WellKnown("http")),
Expand Down Expand Up @@ -300,6 +301,7 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
("js_node_http_server_ref", OwnerKind::WellKnown("http")),
("js_node_http_server_unref", OwnerKind::WellKnown("http")),
("js_node_http_im_on", OwnerKind::WellKnown("http")),
("js_node_http_im_once", OwnerKind::WellKnown("http")),
("js_node_http_im_pause", OwnerKind::WellKnown("http")),
("js_node_http_im_resume", OwnerKind::WellKnown("http")),
("js_node_http_im_pause_self", OwnerKind::WellKnown("http")),
Expand Down Expand Up @@ -549,6 +551,8 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
("js_event_emitter_new", OwnerKind::WellKnown("events")),
("js_event_emitter_new_with_options", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_new", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_call", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_subclass_init", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_async_id", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_trigger_async_id", OwnerKind::WellKnown("events")),
("js_event_emitter_async_resource_async_resource", OwnerKind::WellKnown("events")),
Expand Down Expand Up @@ -1126,6 +1130,8 @@ mod tests {
"js_event_emitter_set_max_listeners",
"js_event_emitter_get_max_listeners",
"js_event_emitter_domain_value",
"js_event_emitter_async_resource_call",
"js_event_emitter_async_resource_subclass_init",
] {
assert_symbol_routes_to(symbol, OwnerKind::WellKnown("events"));
}
Expand Down
19 changes: 17 additions & 2 deletions crates/perry-codegen/src/lower_call/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,15 @@ pub(super) fn lower_builtin_new<'a>(
// the native-module call table used by `dns.Resolver()`. Route it
// to the same runtime constructor and preserve evaluation of any
// superfluous arguments.
for arg in args {
let options_idx = adopt_optional_arg(ctx, args, 0, group)?;
for arg in args.iter().skip(1) {
let _ = lower_expr(ctx, arg)?;
}
let options = match options_idx {
Some(index) => group.reread(ctx, index)?,
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),
};
let options = group.adopt_emitted(ctx, crate::rooting::Repr::Boxed, &options, true);
let runtime = if import_src.is_some_and(|source| {
source.strip_prefix("node:").unwrap_or(source) == "dns/promises"
}) {
Expand All @@ -157,7 +163,16 @@ pub(super) fn lower_builtin_new<'a>(
};
ctx.pending_declares
.push((runtime.to_string(), DOUBLE, vec![I64]));
Ok(Some(ctx.block().call(DOUBLE, runtime, &[(I64, "0")])))
let zero = "0".to_string();
let args_array = group.begin_array(ctx, &zero);
let options = group.reread_emitted(ctx, options);
group.push_array(ctx, args_array, &options);
let args_array = group.read_array(ctx, args_array);
Ok(Some(ctx.block().call(
DOUBLE,
runtime,
&[(I64, &args_array)],
)))
}
"Utf8Stream"
if import_src
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ pub(super) const HTTP_CLIENT_ROWS: &[NativeModSig] = &[
has_receiver: true,
method: "once",
class_filter: Some("ClientRequest"),
runtime: "js_http_on",
runtime: "js_http_once",
args: &[NA_STR, NA_PTR],
ret: NR_PTR,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ pub(super) const HTTP_SERVER_ROWS: &[NativeModSig] = &[
has_receiver: true,
method: "once",
class_filter: Some("IncomingMessage"),
runtime: "js_node_http_im_on",
runtime: "js_node_http_im_once",
args: &[NA_STR, NA_PTR],
ret: NR_F64,
},
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/runtime_decls/stdlib_ffi/net_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub(crate) fn declare_net_http(module: &mut LlModule) {
module.declare_function("js_https_get_overload", I64, &[I64]);
module.declare_function("js_https_request_overload", I64, &[I64]);
module.declare_function("js_http_on", I64, &[I64, I64, I64]);
module.declare_function("js_http_once", I64, &[I64, I64, I64]);
module.declare_function("js_http_request", I64, &[DOUBLE, I64]);
module.declare_function("js_http_request_body", I64, &[I64]);
module.declare_function("js_http_request_body_length", DOUBLE, &[I64]);
Expand Down Expand Up @@ -222,6 +223,7 @@ pub(crate) fn declare_net_http(module: &mut LlModule) {
module.declare_function("js_node_http_im_resume", VOID, &[I64]);
module.declare_function("js_node_http_im_destroy", VOID, &[I64]);
module.declare_function("js_node_http_im_on", DOUBLE, &[I64, I64, I64]);
module.declare_function("js_node_http_im_once", DOUBLE, &[I64, I64, I64]);
module.declare_function("js_node_http_im_read", DOUBLE, &[I64]);
module.declare_function("js_node_http_im_set_timeout", I64, &[I64, DOUBLE, I64]);
// ServerResponse:
Expand Down
Loading
Loading