Skip to content

One detached rejection rejects every pending CallAsync call while targets keep running #145

Open

Description

@RichardHoekstra

Reproduction

Start eight overlapping CallAsync calls in one runner. Seven await 50 ms; one awaits 25 ms and schedules a detached rejected Promise after 10 ms. Collect caller outcomes, wait another 80 ms, then read completion counters.

The retained fixture is async-targets.ts, async-probe.whlib and tests/unhandled-isolation.whscr.

Observed behavior

Case Caller outcomes Final completed count
One target throws through its returned promise One rejected, seven fulfilled 8
One target creates a detached rejection All eight rejected with the same error 8

Both results reproduce on the official 6.2 image for GitHub main a91a7a28, with the stock implementation. Targets use synthetic timers/counters, not application writes. Older 6.0/6.1 runs show the same behavior.

Cause

The runner's process-level handler explicitly sends each unhandled rejection to every pending message ID. It does not stop those target invocations.

A caller therefore cannot infer that failed work stopped. Retrying a target with side effects could repeat them; duplicate writes were not tested.

Decision needed

Should an unowned rejection be reported separately, attributed to an invocation, or fail the runner and its co-resident calls? Define what happens to running targets in each case.

No fix is proposed yet. The intended failure boundary needs agreement; the current reproduction characterizes the existing behavior.

Activity

  1. unilynx commented on Oct 27, 2026

    @unilynx
    Member

    Should an unowned rejection be reported separately, attributed to an invocation, or fail the runner and its co-resident calls? Define what happens to running targets in each case.

    An uncaught rejection/exception puts an application in an unforeseen state. Browsers don't mind very much. nodejs would by default kill the whole application and strongly recommends you do not try to keep running after an uncaught rejection.

    I think it's best to just fail the runner completely to force users to deal with this and reduce the number of unexpected states you would have to deal with.

    Aside from that we consider callasync/ImportJS a transitionary mechanism. it was one of the earliest methods to integrate JS into HS, but it has a high startup overhead. The general recommendation is to use CallJS or setup a backendService if you need the persistence.

  2. RichardHoekstra commented on Oct 27, 2026

    @RichardHoekstra
    ContributorAuthor

    Runnable reproduction: native caller, TS targets and instructions, with a disposable Docker runner.

    From that checkout:

    python3 whtree/modules/platform/scripts/repro/run.py --docker 145

    On the pinned official 6.2 image for a91a7a28:

    • One returned rejection: 1 rejected caller, 7 fulfilled callers, 8 completed targets.
    • One detached rejection: 8 rejected callers, 0 fulfilled callers, 8 completed targets.

    Both completion checks reach the same runner process. This makes the continued target execution observable; it does not require unrelated calls to survive an unhandled rejection. A fatal-runner implementation can define its regression expectations from there.

  3. unilynx commented on Oct 28, 2026

    @unilynx
    Member

    Runnable reproduction: native caller, TS targets and instructions, with a disposable Docker runner.

    From that checkout:

    python3 whtree/modules/platform/scripts/repro/run.py --docker 145
    On the pinned official 6.2 image for a91a7a28:

    • One returned rejection: 1 rejected caller, 7 fulfilled callers, 8 completed targets.
    • One detached rejection: 8 rejected callers, 0 fulfilled callers, 8 completed targets.

    Both completion checks reach the same runner process. This makes the continued target execution observable; it does not require unrelated calls to survive an unhandled rejection. A fatal-runner implementation can define its regression expectations from there.

    I'm not sure what you're trying to say here. Can we have a non-AI answer about what the actual problem is now ?

  4. RichardHoekstra commented on Oct 28, 2026

    @RichardHoekstra
    ContributorAuthor

    There’s no new issue here. I added the follow-up with the actual scripts I ran.

    I agree with terminating the whole runner on an unhandled rejection and having the caller handle the failure and its consequences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions