Report worker termination to channel tasks - #28
Open
paberr wants to merge 2 commits into
Open
Conversation
Owner
Author
A terminated worker left its channel task stuck: `result()` panicked on the dropped sender and `recv()` blocked forever, because the channel's forwarding closure is leaked and its sender is never dropped. `result()` now returns `Result<R, TaskError>` and `recv()` returns `None` once the worker is gone. `WebWorker::terminate()` makes this explicit and is the only way to stop work that does not cooperate. Co-authored-by: Robert Schütte <schuetterobert@gmail.com>
paberr
force-pushed
the
channel-task-worker-termination
branch
from
August 14, 2026 12:43
e95daae to
2f1c9bd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A channel task could not survive the death of its worker:
ChannelTask::result()ended in.expect("WebWorker result sender dropped"), so aterminated worker turned a pending
result()into a panic. Dropping aWebWorkeris enough to trigger this, since
Dropalready callsworker.terminate().ChannelTask::recv()could never returnNone.Channel::fromcallscallback_handle.forget(), so thempscsender is leaked and the receiver nevercloses. On a dead worker
recv()blocks forever. The doc comment claiming it returnsNone"if the worker has finished and closed the channel" described a path that didnot exist.
Both are reachable today without any new API, so worker death was effectively
unobservable: one branch panicked, the other hung.
Changes
result()returnsResult<R, TaskError>;TaskError::WorkerTerminatedreports thatthe worker went away before returning a result.
recv()/recv_bytes()returnNoneonce the worker is terminated. Messages thatalready arrived are still handed out first, so nothing in flight is lost.
WebWorker::terminate()andis_terminated()are now public.terminate()closes theport, terminates the worker and drops the pending task senders;
Dropjust calls it.run/run_channelon a terminated worker fails fast with a clear messageinstead of posting into a dead port and hanging forever.
Terminating is the only way to stop work that does not cooperate — a computation without
a cancellation point, or a task that spawned background work of its own. Cooperative
cancellation over the channel stays the recommended path for everything else, since
terminating discards a warm worker.
Scope
This is the subset of #22 that is a bug fix. It deliberately leaves out
ChannelTask::terminate()and the pool's exclusive worker leases: killing a worker froma task handle is only safe when the task owns that worker, and making every pool channel
task own one blocks ordinary
pool.run()calls for as long as a channel task lives.The use case behind #22 is served here by running such a task on its own
WebWorker.pool.run_channel()is unchanged and keeps sharing workers.Breaking changes
ChannelTask::result()returnsResult<R, TaskError>.ChannelTask::new()is nowpub(crate)(it waspubbut#[doc(hidden)], andcallers outside the crate could not construct its arguments).
No version bump in this PR — happy to add one if you want it in the same commit.
Closes nothing on its own; #22 remains open for the termination API discussion.
🤖 Generated with Claude Code
Unrelated: clippy fix
The second commit silences
clippy::clone_on_copyon the getters that#[wasm_bindgen(getter_with_clone)]generates forWorkerPoolOptions. This is apre-existing failure on
mainthat a recent nightly clippy started reporting, notsomething this PR introduces — the only other change here to
src/pool/mod.rsis adoc-comment line. It is in its own commit so it can be split off and landed on
mainseparately if you prefer;
mainneeds it either way.