Skip to content

fix(abort): park a dropped signal's receivers instead of spinning - #45

Merged
butterflyfish merged 1 commit into
masterfrom
fix/abort-signal-spin
Sep 11, 2026
Merged

butterflyfish merged 1 commit into
masterfrom
fix/abort-signal-spin

Conversation

@butterflyfish

@butterflyfish butterflyfish commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • AbortReceiver::recv looped on watch::Receiver::changed() and ignored its result. When the AbortSignal (the watch sender) is dropped without aborting, changed() returns Err on every poll, so the loop never waits and the task awaiting recv() runs at full CPU. recv() now parks once the channel is closed, because a dropped signal can never abort.
  • fetch spawns an abort bridge (select! over recv() and a stop notify) for every request that carries a signal. On a normal response the bridge is never stopped, so it outlived the request, and once the JS signal was collected it became one of those spinning tasks. The bridge now also ends when the transport drops its receiver (tx.closed()).
  • Regression test a_dropped_signal_parks_its_receivers_instead_of_spinning: counts recv() polls for 50 ms after the signal is dropped.

Validation

  • GitHub Actions CI passed: fmt, typegen, msrv, host-verify QuickJS (Windows / Ubuntu / macOS), host-verify JavaScriptCore (macOS), and jscore-source-* on macOS / Linux / Windows.
  • Local: cargo test -p rong_abort --features quickjs (6 passed; the new test fails without the fix with recv() was polled 301 times in 50ms), cargo fmt --check -p rong_abort -p rong_http, and cargo clippy -p rong_abort -p rong_http --lib --tests --no-default-features --features rong_abort/quickjs,rong_http/quickjs -- -D warnings.
  • Not run locally: full cargo make ci-verify. rong_http's test_fetch fails the same 13 of 19 JS cases on this machine with and without this change (local test server requests return "fetch failed"), so abort-during-body-read is left to host CI.

Not applicable (no changes in those areas): JSC artifact pin, npm packages, ArkJS / OHOS, Harmony.

Release Impact

User-facing bugfix: a dropped AbortSignal could pin a JS worker at 100% CPU, and fetch left its abort-bridge task alive after the request. No crate version bump in this PR (same as other module fixes). Changelog and rong_abort / rong_http patch publish can follow so LingXia can drop a [patch].

Notes

  • Found in a LingXia Android product app whose HTTP transport gives every request an AbortController for its timeout. On the phone, JS worker threads sat at ~100% CPU, and the number grew from one to four as requests accumulated. A sample of the macOS Runner showed the hot worker inside rong_http::fetch → rong_abort::abort_signal::recv → tokio::sync::watch → coop.
  • With these crates patched in, a launcher cold start of the same app showed no hot thread across five samples over 100 s. One earlier short sample on a patched build still showed a busy thread, and it did not come back in the longer sampling.

AbortReceiver::recv looped on watch::Receiver::changed() and ignored its
result. Once the AbortSignal, which owns the watch sender, was dropped
without aborting, changed() returned Err on every poll and the loop never
waited: whichever task awaited recv() ran at full CPU. fetch keeps such a
task alive for every request that carries a signal (the abort bridge), so
an app that gives its requests an AbortController pinned a JS worker for
each signal it let go.

recv() now parks once the channel is closed, since a dropped signal can no
longer abort. fetch's abort bridge also ends when the transport drops its
receiver, instead of outliving every request that carried a signal.
@butterflyfish
butterflyfish merged commit ee8adfc into master Sep 11, 2026
13 checks passed
@butterflyfish
butterflyfish deleted the fix/abort-signal-spin branch September 11, 2026 10:57
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