Skip to content

Set module internal promise rejection as handled when appropriate#534

Open
xim wants to merge 1 commit into
bellard:masterfrom
xim:module-rejection-handled
Open

Set module internal promise rejection as handled when appropriate#534
xim wants to merge 1 commit into
bellard:masterfrom
xim:module-rejection-handled

Conversation

@xim

@xim xim commented Jul 1, 2026

Copy link
Copy Markdown

Before:

A module body is run as an async function. js_execute_sync_module calls that function. It finds its result promise already rejected, reads the result, and frees it without attaching a JS handler. The rejection fires a unhandled notification. That notification is never balanced by a is_handled=true notification. The internal promise rejection surfaces as an unhandled rejection in addition to the module's evaluation promise.

Additional symptoms:

A dynamic import() whose rejection is fully handled by the caller still leaked a unhandled rejection: the internal body promise stayed orphaned. Without the fix a import('m').then(ok, onerr) of a throwing module results in a unhandled rejection.

Fix:

Mark the consumed promise handled, emitting the balancing notification with is_handled=true so the host can handle the rejection correctly.

With the fix, a import('m').then(ok, onerr) nets zero false positives.

Added a regression test in tests/test_std.js covering the dynamic import case (this repo has no api-test harness).

Backported from quickjs-ng PR:
quickjs-ng/quickjs#1554

Before:

A module body is run as an async function. js_execute_sync_module calls
that function. It finds its result promise already rejected, reads the
result, and frees it without attaching a JS handler. The rejection fires
a unhandled notification. That notification is never balanced by a
is_handled=true notification. The internal promise rejection surfaces as
an unhandled rejection in addition to the module's evaluation promise.

Additional symptoms:

A dynamic import() whose rejection is fully handled by the caller still
leaked a unhandled rejection: the internal body promise stayed orphaned.
Without the fix a `import('m').then(ok, onerr)` of a throwing module
results in a unhandled rejection.

Fix:

Mark the consumed promise handled, emitting the balancing notification
with is_handled=true so the host can handle the rejection correctly.

With the fix, a `import('m').then(ok, onerr)` nets zero false positives.

Added a regression test in tests/test_std.js covering the dynamic import
case (this repo has no api-test harness).

Backported from quickjs-ng PR:
quickjs-ng/quickjs#1554

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xim xim force-pushed the module-rejection-handled branch from 48e2939 to 23792d2 Compare July 2, 2026 11:29
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.

1 participant