Skip to content

Report worker termination to channel tasks - #28

Open
paberr wants to merge 2 commits into
mainfrom
channel-task-worker-termination
Open

Report worker termination to channel tasks#28
paberr wants to merge 2 commits into
mainfrom
channel-task-worker-termination

Conversation

@paberr

@paberr paberr commented Aug 14, 2026

Copy link
Copy Markdown
Owner

A channel task could not survive the death of its worker:

  • ChannelTask::result() ended in .expect("WebWorker result sender dropped"), so a
    terminated worker turned a pending result() into a panic. Dropping a WebWorker
    is enough to trigger this, since Drop already calls worker.terminate().
  • ChannelTask::recv() could never return None. Channel::from calls
    callback_handle.forget(), so the mpsc sender is leaked and the receiver never
    closes. On a dead worker recv() blocks forever. The doc comment claiming it returns
    None "if the worker has finished and closed the channel" described a path that did
    not exist.

Both are reachable today without any new API, so worker death was effectively
unobservable: one branch panicked, the other hung.

Changes

  • result() returns Result<R, TaskError>; TaskError::WorkerTerminated reports that
    the worker went away before returning a result.
  • recv() / recv_bytes() return None once the worker is terminated. Messages that
    already arrived are still handed out first, so nothing in flight is lost.
  • WebWorker::terminate() and is_terminated() are now public. terminate() closes the
    port, terminates the worker and drops the pending task senders; Drop just calls it.
  • Calling run/run_channel on a terminated worker fails fast with a clear message
    instead 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 from
a 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() returns Result<R, TaskError>.
  • ChannelTask::new() is now pub(crate) (it was pub but #[doc(hidden)], and
    callers 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_copy on the getters that
#[wasm_bindgen(getter_with_clone)] generates for WorkerPoolOptions. This is a
pre-existing failure on main that a recent nightly clippy started reporting, not
something this PR introduces — the only other change here to src/pool/mod.rs is a
doc-comment line. It is in its own commit so it can be split off and landed on main
separately if you prefer; main needs it either way.

@paberr

paberr commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

Also, thanks a lot to @Roba1993 for bringing up the issue and opening the initial PR! 👏
@Roba1993 If you want to have a look and see if this is useful to you, I'd appreciate it!

paberr and others added 2 commits August 14, 2026 12:43
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
paberr force-pushed the channel-task-worker-termination branch from e95daae to 2f1c9bd Compare August 14, 2026 12:43
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