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
20 changes: 20 additions & 0 deletions crates/perry-hir/src/lower/expr_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,26 @@ fn lower_member_inner(ctx: &mut LoweringContext, member: &ast::MemberExpr) -> Re
object: Box::new(object_expr),
property: property_name,
});
} else if module_name == "worker_threads"
&& class_name == "MessageChannel"
&& matches!(property_name.as_str(), "port1" | "port2")
{
// #3157: `new MessageChannel()` returns a real heap object
// `{ port1, port2 }`. Reading `chan.port1` must be a plain
// object field load (returning the port object, whose
// methods are closure-valued fields) — NOT a zero-arg
// native getter, which would discard the same-process
// paired-port delivery. The port objects themselves are not
// registered native instances, so `port.postMessage(...)` /
// `port.on(...)` already lower as ordinary object-method
// calls (invoking the bound closures). parentPort stays on
// the native-receiver dispatch path (it's a singleton
// handle, not a real object).
let object_expr = lower_expr(ctx, &member.obj)?;
return Ok(Expr::PropertyGet {
object: Box::new(object_expr),
property: property_name,
});
} else if matches!(
module_name.as_str(),
"readable_stream"
Expand Down
31 changes: 31 additions & 0 deletions crates/perry-hir/src/lower/expr_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,37 @@ pub(super) fn lower_new(ctx: &mut LoweringContext, new_expr: &ast::NewExpr) -> R
});
}

// #3157: `import { MessageChannel } from "worker_threads"` then
// `new MessageChannel()` — the bare-ident form must route to the
// same receiver-less worker_threads NativeMethodCall as the
// `new worker_threads.MessageChannel()` member form above, so the
// runtime `js_worker_threads_message_channel_new` allocates the
// real `{ port1, port2 }` object. Without this it falls through to
// the user-class `Expr::New` path and gets an empty object.
if matches!(
ctx.lookup_native_module(&class_name),
Some(("worker_threads", Some("MessageChannel")))
| Some(("worker_threads", Some("BroadcastChannel")))
) {
let args = new_expr
.args
.as_ref()
.map(|args| {
args.iter()
.map(|a| lower_expr(ctx, &a.expr))
.collect::<Result<Vec<_>>>()
})
.transpose()?
.unwrap_or_default();
return Ok(Expr::NativeMethodCall {
module: "worker_threads".to_string(),
class_name: None,
object: None,
method: class_name,
args,
});
}

if matches!(class_name.as_str(), "MIMEType" | "MIMEParams") {
if let Some((module_name, Some(method_name))) =
ctx.lookup_native_module(&class_name)
Expand Down
10 changes: 10 additions & 0 deletions crates/perry-stdlib/src/common/async_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,11 @@ pub extern "C" fn js_stdlib_process_pending() -> i32 {
// Process pending worker_threads messages (stdin reader)
count += crate::worker_threads::js_worker_threads_process_pending();

// Drain same-process MessageChannel port inboxes (#3157) — dispatch queued
// `port.postMessage(v)` payloads to `port.on('message', cb)` listeners and
// fire `close` events for closed ports.
count += crate::worker_threads::js_worker_threads_channels_process_pending();

// Process pending readline lines (#347 Phase 1) — drains the stdin
// reader's queue and dispatches to question/line/close callbacks.
count += crate::readline::js_readline_process_pending();
Expand Down Expand Up @@ -674,6 +679,11 @@ pub extern "C" fn js_stdlib_has_active_handles() -> i32 {
if crate::readline::js_readline_has_active() != 0 {
return 1;
}
// Same-process MessageChannel ports (#3157) — keep the loop alive while a
// started port still has queued messages or a pending `close` event.
if crate::worker_threads::js_worker_threads_channels_has_pending() != 0 {
return 1;
}
// Bundled-fastify — keep the loop alive while any FastifyServerHandle
// is in the "listening" state. Paired with
// `js_fastify_process_pending` in `js_stdlib_process_pending` above
Expand Down
Loading
Loading