Skip to content

fix(stdlib): build with --no-default-features again, unbreaking the auto-optimize relink (#7764) - #7772

Open
proggeramlug wants to merge 1 commit into
mainfrom
fix/7764-stdlib-no-default-features
Open

fix(stdlib): build with --no-default-features again, unbreaking the auto-optimize relink (#7764)#7772
proggeramlug wants to merge 1 commit into
mainfrom
fix/7764-stdlib-no-default-features

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #7764.

What was broken

cargo build -p perry-stdlib --no-default-features produced 12 errors. That is the configuration the auto-optimize relink builds with, so every perry compile that triggered auto-optimize fell back to the prebuilt archives with a warning, and any ad-hoc build needed PERRY_NO_AUTO_OPTIMIZE=1.

Both causes violate the contract common/mod.rs already states in prose:

Tokio-backed promise/runtime bridge … Always-on code that references it must also be #[cfg(feature = "async-runtime")]-gated.

Cause 1 — one line, exactly as diagnosed (#7745)

js_set_native_events_dispatch referenced crate::events without the #[cfg(feature = "bundled-events")] that gates the module. The neighbouring registrations in the same function are gated — database-sqlite on the very next line — which is what makes this an omission rather than a decision. One #[cfg].

Cause 2 — eleven sites the issue listed as "the same shape"

They are the same shape but not the same fix. worker_threads is always-on and reaches common::async_bridge in eleven places across five files, and neither obvious repair works:

So worker_threads/async_shim.rs supplies the four entry points in both configurations — forwarding to async_bridge when it is compiled in, settling inline when it is not.

That inline arm is not invented semantics. The queue exists to hand work to the pump; with no pump there is nothing to hand it to, and doing the same work synchronously reaches the same observable end state (the promise settles). The pinning js_promise_new_for_native_resolution performs is likewise a consequence of deferral — it guards the window between creation and the pump's resolution — and an inline settle spans no collection point, so a plain js_promise_new is its correct counterpart. Each of those arguments is written down at the shim.

Validation

  • cargo build -p perry-stdlib --no-default-features: 0 errors (was 12).

  • cargo build -p perry-stdlib (default features): 0 errors.

    Both directions deliberately: my first cut of the shim accidentally imported itself, and --no-default-features could not see it because that arm is #[cfg]'d out. Checking only the configuration named in the issue would have shipped it.

  • The actual payoff, checked end to end: perry compile of a small program now completes the auto-optimize relink with no fallback warning and runs correctly.

  • cargo test -p perry-stdlib --lib: 111 passed, 0 failed.

  • Targeted parity across worker-threads / events / message-port / broadcast / async / promise gap tests: all pass.

  • cargo fmt --all --check, scripts/check_file_size.sh: clean.

One process note: a test_gap_worker COMPILE_FAIL appeared once and did not reproduce — it raced a rebuild that was still writing the archives. Re-run twice clean before believing that shape of failure.

No version bump.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed builds when optional default features are disabled.
    • Improved compatibility for worker-thread messaging and promise handling across feature configurations.
    • Prevented event-dispatch initialization from referencing unavailable functionality.
  • Documentation
    • Added a changelog entry describing the feature-configuration build fixes.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

perry-stdlib now builds with --no-default-features. Event dispatch registration is feature-gated. Worker thread async operations use a shim that forwards to async_bridge when enabled and settles promises inline otherwise.

Changes

stdlib feature compatibility

Layer / File(s) Summary
Feature compatibility contracts
crates/perry-stdlib/src/common/dispatch/init.rs, crates/perry-stdlib/src/worker_threads/async_shim.rs
Event dispatch registration now requires bundled-events. The worker async shim forwards to async_bridge with async-runtime and provides inline implementations without it.
Worker async wiring
crates/perry-stdlib/src/worker_threads.rs, crates/perry-stdlib/src/worker_threads/*, changelog.d/7772-stdlib-no-default-features.md
Worker thread promise creation, resolution, deferred resolution, and pump registration use the async shim. The changelog documents the build fix.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • PerryTS/perry#7745: Added the events dispatch bridge that this change now feature-gates.

Suggested labels: bug, rust

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix: restoring perry-stdlib builds with --no-default-features and auto-optimize relinking.
Description check ✅ Passed The description explains the causes, implementation, linked issue, validation results, and impact, although it does not use every template heading.
Linked Issues check ✅ Passed The PR fixes both feature-gating failures, restores no-default-feature builds, and preserves worker-thread FFI symbols required by issue #7764.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope and directly support feature-gated builds, worker-thread behavior, and auto-optimize relinking.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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/7764-stdlib-no-default-features

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.

@proggeramlug
proggeramlug force-pushed the fix/7764-stdlib-no-default-features branch from 85c5616 to 60ed63a Compare August 10, 2026 13:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-stdlib/src/worker_threads.rs`:
- Around line 973-974: The immediate-termination path around
js_promise_new_for_native_resolution must balance the Promise pin when
resolved_now is true. Route creation and settlement through the async_shim in
both branches, or invoke unpin_promise_after_native_resolution() before the
direct perry_runtime::js_promise_resolve() only under async-runtime, while
preserving normal pending-worker processing.

In `@crates/perry-stdlib/src/worker_threads/async_shim.rs`:
- Around line 44-50: The async-runtime Promise lifecycle is unbalanced between
deferred and immediate settlement paths. Update
js_promise_new_for_native_resolution in
crates/perry-stdlib/src/worker_threads/async_shim.rs:44-50 and the immediate
js_promise_resolve path in crates/perry-stdlib/src/worker_threads.rs:973-974 so
the Promise remains rooted until queued worker resolutions finish, or is
explicitly unpinned before immediate settlement, matching the bridge contract.
- Around line 52-73: Update queue_deferred_resolution and the corresponding
async bridge so converter is never executed on the spawned
wait_for_direct_message_ack thread. Dispatch the conversion to the
main-thread/runtime context, then call queue_promise_resolution with the
converted value there; preserve the existing success/rejection behavior and
call-site compatibility.
- Around line 39-43: Update the no-async-runtime inline ensure_pump_registered()
path so it registers the stdlib worker pump and active-handle hooks instead of
remaining a no-op. Keep Tokio-specific runtime setup gated by async-runtime, and
preserve the unconditional callers from js_worker_threads_worker_new() and
js_worker_threads_message_channel_new() so queued worker events, parentPort
messages, and termination promises continue draining.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b939eadb-f308-4f5e-a8de-eb2cebb22ded

📥 Commits

Reviewing files that changed from the base of the PR and between 423bb44 and 60ed63a.

📒 Files selected for processing (8)
  • changelog.d/7772-stdlib-no-default-features.md
  • crates/perry-stdlib/src/common/dispatch/init.rs
  • crates/perry-stdlib/src/worker_threads.rs
  • crates/perry-stdlib/src/worker_threads/async_shim.rs
  • crates/perry-stdlib/src/worker_threads/broadcast_channel.rs
  • crates/perry-stdlib/src/worker_threads/direct_message.rs
  • crates/perry-stdlib/src/worker_threads/message_port.rs
  • crates/perry-stdlib/src/worker_threads/worker_pump.rs

Comment on lines +973 to +974
let promise =
unsafe { crate::worker_threads::async_shim::js_promise_new_for_native_resolution() };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files of interest:"
fd -a 'worker_threads\.rs|async_bridge\.rs|async_shim\.rs' . | sed 's#^\./##'

echo
echo "Inspect worker_threads around lines 950-1015:"
if [ -f crates/perry-stdlib/src/worker_threads.rs ]; then
  sed -n '940,1025p' crates/perry-stdlib/src/worker_threads.rs | cat -n
fi

echo
echo "Search for async_shim symbols:"
rg -n "queue_promise_resolution|js_promise_resolve|js_promise_new_for_native_resolution|promise_unpin|native_resolution|pin|resolve" crates/perry-stdlib/src crates -g '*.rs' | head -n 200

Repository: PerryTS/perry

Length of output: 26635


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "async_shim outline:"
ast-grep outline crates/perry-stdlib/src/worker_threads/async_shim.rs --view expanded | sed -n '1,220p' || true
echo
echo "async_shim relevant content:"
sed -n '1,260p' crates/perry-stdlib/src/worker_threads/async_shim.rs | cat -n

echo
echo "async_bridge outline:"
ast-grep outline crates/perry-stdlib/src/common/async_bridge.rs --view expanded | sed -n '1,240p' || true
echo
echo "async_bridge relevant content:"
sed -n '1,260p' crates/perry-stdlib/src/common/async_bridge.rs | cat -n

echo
echo "worker_threads module/header relevant content:"
sed -n '1,80p' crates/perry-stdlib/src/worker_threads.rs | cat -n
sed -n '680,790p' crates/perry-stdlib/src/worker_threads.rs | cat -n

Repository: PerryTS/perry

Length of output: 28996


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect async_bridge resolution/settle paths:"
sed -n '350,430p' crates/perry-stdlib/src/common/async_bridge.rs | cat -n
echo
echo "Inspect process_pending and pending drain:"
sed -n '440,530p' crates/perry-stdlib/src/common/async_bridge.rs | cat -n
echo
echo "Search unpin_promise_after_native_resolution:"
rg -n "unpin_promise_after_native_resolution|js_stdlib_process_pending" crates/perry-stdlib/src crates -g '*.rs'

Repository: PerryTS/perry

Length of output: 17175


Balance the native-resolution promise on immediate termination.

When async-runtime is enabled, js_promise_new_for_native_resolution() pins the Promise. The resolved_now branch calls perry_runtime::js_promise_resolve() directly, so unpin_promise_after_native_resolution() is never run through js_stdlib_process_pending(). Repeated termination of exited workers leaves pinned Promises that cannot be collected. Route native Promise creation and settlement through async_shim in both branches, or add a branch that directly calls unpin_promise_after_native_resolution() before resolve only for the async-runtime path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/worker_threads.rs` around lines 973 - 974, The
immediate-termination path around js_promise_new_for_native_resolution must
balance the Promise pin when resolved_now is true. Route creation and settlement
through the async_shim in both branches, or invoke
unpin_promise_after_native_resolution() before the direct
perry_runtime::js_promise_resolve() only under async-runtime, while preserving
normal pending-worker processing.

Source: MCP tools

Comment on lines +39 to +43
#[cfg(not(feature = "async-runtime"))]
mod inline {
/// No bridge means no pump to register.
pub(crate) fn ensure_pump_registered() {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 \
  'ensure_pump_registered|js_register_stdlib_pump|js_register_stdlib_has_active|js_worker_threads_(process_pending|has_pending|channels_process_pending|channels_has_pending)' \
  crates/perry-stdlib crates/perry-runtime

Repository: PerryTS/perry

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- worker async_shim feature gates ---\n'
sed -n '1,140p' crates/perry-stdlib/src/worker_threads/async_shim.rs

printf '\n--- worker pump outline and relevant sections ---\n'
ast-grep outline crates/perry-stdlib/src/worker_threads/worker_pump.rs --view expanded || true
rg -n -C 8 'js_worker_threads_(process_pending|has_pending)|start_worker|start_stdin_reader|worker_threads_process_pending|worker_threads_has_pending|ensure_pump_registered|queue_promise'| crates/perry-stdlib/src/worker_threads src/worker_threads crates/perry-runtime/src/common crates/perry-runtime/src/event_pump 2>/dev/null || true

printf '\n--- async_bridge registrations and active/pending methods ---\n'
sed -n '430,475p' crates/perry-stdlib/src/common/async_bridge.rs
rg -n -C 5 'js_register_wait_driver|ensure_gc_scanner_registered|js_stdlib_init_dispatch|ensure_pump_registered|js_run_stdlib_pump|js_stdlib_process_pending|js_stdlib_has_active_handles' crates/perry-runtime crates -g '*.rs' | head -240

printf '\n--- Cargo features for perry-stdlib/runtime ---\n'
rg -n 'async-runtime|perry-stdlib|perry-runtime' Cargo.toml crates -g 'Cargo.toml' | head -160

Repository: PerryTS/perry

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- worker async_shim feature gates ---'
sed -n '1,140p' crates/perry-stdlib/src/worker_threads/async_shim.rs

printf '%s\n' ''
printf '%s\n' '--- worker pump outline and relevant sections ---'
ast-grep outline crates/perry-stdlib/src/worker_threads/worker_pump.rs --view expanded || true
rg -n -C 8 'js_worker_threads_(process_pending|has_pending)|start_worker|start_stdin_reader|worker_threads_process_pending|worker_threads_has_pending|ensure_pump_registered|queue_promise' \
  crates/perry-stdlib/src/worker_threads crates/perry-runtime/src/common crates/perry-runtime/src/event_pump 2>/dev/null || true

printf '%s\n' ''
printf '%s/n' '--- async_bridge registrations and active/pending methods ---'
sed -n '430,475p' crates/perry-stdlib/src/common/async_bridge.rs
rg -n -C 5 'js_register_wait_driver|ensure_gc_scanner_registered|js_stdlib_init_dispatch|ensure_pump_registered|js_run_stdlib_pump|js_stdlib_process_pending|js_stdlib_has_active_handles' \
  crates/perry-runtime crates -g '*.rs' | head -240

printf '%s\n' ''
printf '%$s\n' '--- Cargo features for perry-stdlib/runtime ---'
rg -n 'async-runtime|perry-stdlib|perry-runtime' Cargo.toml crates -g 'Cargo.toml' | head -160

Repository: PerryTS/perry

Length of output: 46426


Provide a no-default worker pump registration path.

js_worker_threads_worker_new() and js_worker_threads_message_channel_new() call worker_threads::async_shim::ensure_pump_registered() unconditionally. Without async-runtime, the inline shim is a no-op, so worker events/parentPort messages and termination promises can remain queued, and the event loop can exit before they drain. Keep the Tokio runtime setup behind async-runtime, but still register the stdlib pump and active-handle hooks in the no-default path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/worker_threads/async_shim.rs` around lines 39 - 43,
Update the no-async-runtime inline ensure_pump_registered() path so it registers
the stdlib worker pump and active-handle hooks instead of remaining a no-op.
Keep Tokio-specific runtime setup gated by async-runtime, and preserve the
unconditional callers from js_worker_threads_worker_new() and
js_worker_threads_message_channel_new() so queued worker events, parentPort
messages, and termination promises continue draining.

Source: MCP tools

Comment on lines +44 to +50
/// # Safety
/// Mirrors `async_bridge::js_promise_new_for_native_resolution`.
///
/// No pinning: pinning guards the deferral window, and there is none here.
pub(crate) unsafe fn js_promise_new_for_native_resolution() -> *mut perry_runtime::Promise {
perry_runtime::js_promise_new()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg 'crates/perry-stdlib/src/(worker_threads(_test)?|common/async_bridge\.rs)' || true

echo
echo "async_shim outline:"
ast-grep outline crates/perry-stdlib/src/worker_threads/async_shim.rs --view expanded || true

echo
echo "worker_threads relevant sections:"
wc -l crates/perry-stdlib/src/worker_threads.rs crates/perry-stdlib/src/worker_threads/async_shim.rs
sed -n '1,90p' crates/perry-stdlib/src/worker_threads/async_shim.rs
sed -n '930,990p' crates/perry-stdlib/src/worker_threads.rs

echo
echo "Search promise pins/unpins and js_promise_new:"
rg -n "js_promise_new|js_promise_(pin|unpin)|Promise|native_resolution|resolve" crates/perry-stdlib/src crates/perry-rt crates/perry-runtime crates/perry-rt-bindings 2>/dev/null | head -n 200

Repository: PerryTS/perry

Length of output: 28993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate async_bridge docs and perry_runtime Promise APIs:"
rg -n "js_promise_new|js_promise_new_for_native_resolution|pinning|deferral|queued settlement|unpin" crates/perry-stdlib/src crates 2>/dev/null | head -n 300
fd -a 'async_bridge\.rs' . 
fd -a -i 'promise|worker_threads' . | head -n 200

Repository: PerryTS/perry

Length of output: 40429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "async_bridge full:"
cat -n crates/perry-stdlib/src/common/async_bridge.rs | sed -n '1,110p'
cat -n crates/perry-stdlib/src/common/async_bridge.rs | sed -n '470,555p'

echo
echo "worker_threads full context around promised termination:"
cat -n crates/perry-stdlib/src/worker_threads.rs | sed -n '960,1025p'

echo
echo "direct_message call sites:"
cat -n crates/perry-stdlib/src/worker_threads/direct_message.rs | sed -n '1,140p'

echo
echo "promises tests related to 859 native pin:"
cat -n test-files/test_issue_859_native_promise_pin.ts | sed -n '1,220p'

Repository: PerryTS/perry

Length of output: 22648


Keep the bridge Promise contract balanced for async-runtime too.

With async-runtime, js_promise_new_for_native_resolution() now pins the Promise, but the inline immediate-settlement path still resolves it without unpinning. Keep deferral/non-deferral behavior in sync: preserve a GC root until all queued worker resolutions complete, or unpin before the immediate js_promise_resolve() path in the async-runtime fork as well.

📍 Affects 2 files
  • crates/perry-stdlib/src/worker_threads/async_shim.rs#L44-L50 (this comment)
  • crates/perry-stdlib/src/worker_threads.rs#L973-L974
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/worker_threads/async_shim.rs` around lines 44 - 50,
The async-runtime Promise lifecycle is unbalanced between deferred and immediate
settlement paths. Update js_promise_new_for_native_resolution in
crates/perry-stdlib/src/worker_threads/async_shim.rs:44-50 and the immediate
js_promise_resolve path in crates/perry-stdlib/src/worker_threads.rs:973-974 so
the Promise remains rooted until queued worker resolutions finish, or is
explicitly unpinned before immediate settlement, matching the bridge contract.

Source: MCP tools

Comment on lines +52 to +73
/// Settle now rather than queueing for a pump that does not exist.
pub(crate) fn queue_promise_resolution(promise_ptr: usize, is_success: bool, result_bits: u64) {
if promise_ptr == 0 {
return;
}
let promise = promise_ptr as *mut perry_runtime::Promise;
let value = f64::from_bits(result_bits);
if is_success {
perry_runtime::js_promise_resolve(promise, value);
} else {
perry_runtime::js_promise_reject(promise, value);
}
}

/// As above, running the converter inline. The `Send + 'static` bound is
/// kept so the two configurations accept the same call sites.
pub(crate) fn queue_deferred_resolution<F>(promise_ptr: usize, is_success: bool, converter: F)
where
F: FnOnce() -> u64 + Send + 'static,
{
queue_promise_resolution(promise_ptr, is_success, converter());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 10 \
  'queue_(deferred_)?resolution|std::thread::spawn|worker_messaging_error_value|js_promise_(resolve|reject)|js_string_from_bytes|js_error_new' \
  crates/perry-stdlib/src/worker_threads

rg -n -C 8 \
  'thread-local arenas|main thread|converter' \
  crates/perry-stdlib/src/common/async_bridge.rs

Repository: PerryTS/perry

Length of output: 36754


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- Rust files under perry-runtime/thread and promise ---\n'
git ls-files -- crates/perry-runtime | rg 'thread|promise|value|gc' | sed -n '1,200p'

printf '\n--- Runtime imports in async_bridge.rs ---\n'
rg -n 'js_(resolve|reject|promise_)|thread-local|arena|RuntimeRootVisitor|RuntimeHandleScope|main_thread' crates/perry-stdlib/src/common/async_bridge.rs crates/perry-runtime | sed -n '1,240p'

Repository: PerryTS/perry

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Runtime files likely to contain thread arena/runtime context ---'
git ls-files -- crates/perry-runtime | rg 'thread|promise|value|gc|main' | sed -n '1,200p' || true

printf '%s\n' ''
printf '%s\n' '--- Async bridge import/context ---'
sed -n '1,80p' crates/perry-stdlib/src/common/async_bridge.rs

printf '%s\n' ''
printf '%s\n' '--- Direct message rejection path with spawned worker worker ack ---'
sed -n '130,180p' crates/perry-stdlib/src/worker_threads/direct_message.rs

Repository: PerryTS/perry

Length of output: 13680


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- thread.rs arena definitions/usages ---'
sed -n '1,240p' crates/perry-runtime/src/thread.rs

printf '%s\n' ''
printf '%s\n' '--- promise API/resolve/reject definitions ---'
rg -n -C 8 'fn js_promise_(new|resolve|reject|resol|rejec)|js_promise_new_for_native_resolution|RuntimeHandleScope|RuntimeRootVisitor|ThreadLocal|thread_local!' crates/perry-runtime/src/promise crates/perry-runtime/src/gc/roots crates/perry-runtime/src/gc/mod.rs | sed -n '1,260p'

printf '%s\n' ''
printf '%s\n' '--- inline shim resolution functions ---'
sed -n '4,85p' crates/perry-stdlib/src/worker_threads/async_shim.rs

Repository: PerryTS/perry

Length of output: 39964


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- locate RuntimeHandleScope definitions ---'
rg -n -C 8 'struct RuntimeHandleScope|impl RuntimeHandleScope|enum RuntimeHandleScope|RuntimeHandleScope' crates/perry-runtime/src/gc/roots crates/perry-runtime/src/gc/mod.rs crates/perry-runtime/src/gc | sed -n '1,220p'

printf '%s\n' ''
printf '%s\n' '--- locate thread-local arena initializers and allocation symbols ---'
rg -n -C 5 'thread_local|THREAD_LOCAL|PERRY_THREAD_LOCAL|gc_init|GcInit|Arena|alloc|malloc|thread_spawn|worker_thread|spawn' crates/perry-runtime/src/gc/mod.rs crates/perry-runtime/src/gc/roots.rs crates/perry-runtime/src/gc/roots crates/perry-runtime/src/thread.rs crates/perry-runtime/src/promise/mod.rs | sed -n '1,260p'

printf '%s\n' ''
printf '%s\n' '--- direct_message imports and start call paths ---'
sed -n '1,110p' crates/perry-stdlib/src/worker_threads/direct_message.rs
rg -n -C 6 'js_worker_threads_post_message_to_thread|wait_for_direct_message_ack|DirectMessaging|worker_messaging_error_value|queue_worker_messaging_rejection' crates/perry-stdlib/src/worker_threads

Repository: PerryTS/perry

Length of output: 50371


Do not run deferred promise converters on background threads.

js_worker_threads_post_message_to_thread() spawns wait_for_direct_message_ack(), and rejection converts worker_messaging_error_value(...) through queue_deferred_resolution(...). In the fallback shim, that converted js_error_new() runs on the spawned thread; in the async bridge, deferred converters are queued because perry-runtime uses thread-local arenas and the async bridge converters create JS values. Keep main-thread conversion in both shims/bridges so rejected promises do not allocate through the background thread’s runtime context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/worker_threads/async_shim.rs` around lines 52 - 73,
Update queue_deferred_resolution and the corresponding async bridge so converter
is never executed on the spawned wait_for_direct_message_ack thread. Dispatch
the conversion to the main-thread/runtime context, then call
queue_promise_resolution with the converted value there; preserve the existing
success/rejection behavior and call-site compatibility.

Source: MCP tools

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.

perry-stdlib fails to build with --no-default-features (breaks auto-optimize relink)

1 participant