Skip to content

Set module internal promise rejection as handled when appropriate#1554

Open
xim wants to merge 1 commit into
quickjs-ng:masterfrom
xim:rejection-unhandled-notification-dedup
Open

Set module internal promise rejection as handled when appropriate#1554
xim wants to merge 1 commit into
quickjs-ng:masterfrom
xim:rejection-unhandled-notification-dedup

Conversation

@xim

@xim xim commented Jun 30, 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 both JS test and api-test regression tests.

Comment thread api-test.c Outdated
Comment thread quickjs.c Outdated
Comment thread quickjs.c
@saghul

saghul commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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

Can we have a JS test for this?

@xim xim force-pushed the rejection-unhandled-notification-dedup branch from 18b8212 to badcfb6 Compare July 2, 2026 10:50
@xim

xim commented Jul 2, 2026

Copy link
Copy Markdown
Author

Can we have a JS test for this?

I'm having a stab at it now

Edit: Done!

@xim xim force-pushed the rejection-unhandled-notification-dedup branch from efe13bc to 9faff3f Compare July 2, 2026 11:11
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 both JS test and api-test regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Noordhuis <info@bnoordhuis.nl>
@xim xim force-pushed the rejection-unhandled-notification-dedup branch from 9faff3f to cd9a045 Compare July 2, 2026 11:23
xim added a commit to xim/quickjs that referenced this pull request Jul 2, 2026
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>
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.

3 participants