spacedriveapp/spacedrive · error
Worker channel closed trying to pause a not running task
Error message
Worker channel closed trying to pause a not running task
What it means
Worker::pause_not_running_task sends a PauseNotRunningTask message over the worker's bounded(8) command channel and panics via .expect() if the send fails. A send on an async_channel only fails when the receiver side has been dropped, which happens when the worker's message-processing task (spawned in WorkerBuilder::build, crates/task-system/src/worker/mod.rs:64-96) has exited and dropped msgs_rx. So this panic means a pause request was routed to a worker that is already shut down or no longer running.
Source
Thrown at crates/task-system/src/worker/mod.rs:156
&self,
task_id: TaskId,
ack: oneshot::Sender<Result<(), SystemError>>,
) {
self.msgs_tx
.send(WorkerMessage::ResumeTask { task_id, ack })
.await
.expect("Worker channel closed trying to resume task");
}
pub async fn pause_not_running_task(
&self,
task_id: TaskId,
ack: oneshot::Sender<Result<(), SystemError>>,
) {
self.msgs_tx
.send(WorkerMessage::PauseNotRunningTask { task_id, ack })
.await
.expect("Worker channel closed trying to pause a not running task");
}
pub async fn cancel_not_running_task(
&self,
task_id: TaskId,
ack: oneshot::Sender<Result<(), SystemError>>,
) {
self.msgs_tx
.send(WorkerMessage::CancelNotRunningTask { task_id, ack })
.await
.expect("Worker channel closed trying to cancel a not running task");
}
pub async fn force_task_abortion(
&self,
task_id: TaskId,
ack: oneshot::Sender<Result<(), SystemError>>,
) {View on GitHub (pinned to 6dfeccf211)
Solutions
- Stop issuing pause/cancel/abort control calls once shutdown has begun: await system.shutdown() to completion before touching any TaskHandle or controller again
- If you hold Worker directly, never share it across a shutdown boundary; use the SystemHandle-level APIs which check has_shutdown (system.rs:629/653) before dispatching
- Library-level fix: replace .expect with graceful degradation - log a warning and/or return Err(SystemError) indicating the worker is gone, since the pause is meaningless on a dead worker
- In tests, keep the runtime alive until shutdown() completes (await it inside the async test body, not from Drop after the runtime ended)
Example fix
// before (worker/mod.rs:153-156)
self.msgs_tx
.send(WorkerMessage::PauseNotRunningTask { task_id, ack })
.await
.expect("Worker channel closed trying to pause a not running task");
// after
if self
.msgs_tx
.send(WorkerMessage::PauseNotRunningTask { task_id, ack })
.await
.is_err()
{
warn!(%task_id, worker_id = self.id, "Worker channel closed; pause request dropped (worker already shutdown)");
} Defensive patterns
Strategy: validation
Validate before calling
// Gate all task-control calls (pause/cancel/abort) with a shutdown flag you set
// BEFORE calling system.shutdown(); the library's own has_shutdown check
// (system.rs:629/653) cannot see requests already in flight.
use std::sync::atomic::{AtomicBool, Ordering};
static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
fn set_shutting_down() { SHUTTING_DOWN.store(true, Ordering::Release); }
fn can_control_tasks() -> bool { !SHUTTING_DOWN.load(Ordering::Acquire) }
// usage:
// set_shutting_down();
// system.shutdown().await; Try / catch
// Rust has no try/catch; a panic can only be contained via catch_unwind.
// Use only as a boundary around the control call, never as normal flow.
use futures::FutureExt;
use std::panic::AssertUnwindSafe;
let result = AssertUnwindSafe(handle.pause()).catch_unwind().await;
match result {
Ok(_) => {}
Err(_) => {
// Worker channel closed: the pause raced worker shutdown.
// Treat the task as shutdown-suspended; shutdown finalizes all tasks anyway.
}
} Prevention
- Issue pause/cancel/abort only before system.shutdown() starts, and await shutdown to completion before touching TaskHandles again
- Never hold or call Worker/WorkerComm directly; go through SystemHandle dispatch, which checks has_shutdown
- In async tests, await system.shutdown() inside the test body so the runtime never drops worker tasks mid-call
- Do not run task-control calls from Drop impls that may execute during runtime teardown
When it happens
Trigger: Calling TaskRemoteController pause paths (crates/task-system/src/task.rs:433 -> system.rs:310/322 -> Worker::pause_not_running_task at worker/mod.rs:148) concurrently with or after Worker/SystemHandle shutdown: the system dispatch loop checks has_shutdown before routing, but a request already in flight when the worker's respawn loop exits closes the channel and the next send panics. Also triggered if the tokio runtime tears down and drops the worker task while a pause is being dispatched.
Common situations: App/daemon teardown ordering bugs (pausing tasks from a Drop impl or another thread while system.shutdown() runs); #[tokio::test] tests whose runtime drops worker tasks at test end while a pause is still executing; code that keeps TaskHandle controllers alive across a shutdown boundary and calls .pause() afterwards.
Related errors
- Worker channel closed trying to cancel a not running task
- Worker channel closed trying to force task abortion
- Worker channel closed trying to steal task
- Worker channel closed trying to shutdown
- System channel closed trying to report working
AI-assisted analysis of spacedriveapp/spacedrive@6dfeccf211 (2026-08-16).
Data as JSON: /api/errors/f8381f3d168b0f68.
Report an issue: GitHub.