{"record":{"id":"3b3289d1c84963f8","repo":"spacedriveapp/spacedrive","slug":"worker-channel-closed-trying-to-cancel-a-not-runni","errorCode":null,"errorMessage":"Worker channel closed trying to cancel a not running task","messagePattern":"Worker channel closed trying to cancel a not running task","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/task-system/src/worker/mod.rs","lineNumber":167,"sourceCode":"\t\t&self,\n\t\ttask_id: TaskId,\n\t\tack: oneshot::Sender<Result<(), SystemError>>,\n\t) {\n\t\tself.msgs_tx\n\t\t\t.send(WorkerMessage::PauseNotRunningTask { task_id, ack })\n\t\t\t.await\n\t\t\t.expect(\"Worker channel closed trying to pause a not running task\");\n\t}\n\n\tpub async fn cancel_not_running_task(\n\t\t&self,\n\t\ttask_id: TaskId,\n\t\tack: oneshot::Sender<Result<(), SystemError>>,\n\t) {\n\t\tself.msgs_tx\n\t\t\t.send(WorkerMessage::CancelNotRunningTask { task_id, ack })\n\t\t\t.await\n\t\t\t.expect(\"Worker channel closed trying to cancel a not running task\");\n\t}\n\n\tpub async fn force_task_abortion(\n\t\t&self,\n\t\ttask_id: TaskId,\n\t\tack: oneshot::Sender<Result<(), SystemError>>,\n\t) {\n\t\tself.msgs_tx\n\t\t\t.send(WorkerMessage::ForceAbortion { task_id, ack })\n\t\t\t.await\n\t\t\t.expect(\"Worker channel closed trying to force task abortion\");\n\t}\n\n\t#[instrument(skip(self), fields(worker_id = self.id))]\n\tpub async fn shutdown(&self) {\n\t\tif let Some(handle) = self\n\t\t\t.handle\n\t\t\t.try_borrow_mut()","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/crates/task-system/src/worker/mod.rs#L149-L185","documentation":"Worker::cancel_not_running_task sends a CancelNotRunningTask control message to the worker's bounded(8) command channel and panics via .expect() if the channel is closed. The channel closes only when the worker's message loop task has terminated and dropped msgs_rx, i.e. the worker gracefully shut down or its task was dropped by runtime teardown. The panic therefore indicates a cancel request was routed to a worker that can no longer process it.","triggerScenarios":"The cancel chain TaskRemoteController (task.rs:482) -> system dispatch (system.rs:349/367) -> Worker::cancel_not_running_task (worker/mod.rs:159) racing shutdown: the has_shutdown gate is checked before dispatch, but a cancel in flight when the target worker's run loop returns (ShutdownRequest handling in run.rs:90) finds a closed channel. Also fires when the tokio runtime drops the worker task mid-dispatch.","commonSituations":"Canceling tasks from UI/event handlers during daemon shutdown; cancel issued from a task's own Drop while the system is already shutting down; test harnesses that drop the runtime before cancels complete; refactors that moved cancel calls after an await on system.shutdown().","solutions":["Guarantee ordering: once system.shutdown() starts, issue no more cancels; await shutdown before dropping or reusing any task handles","Prefer SystemHandle dispatch APIs (they check has_shutdown at system.rs:629/653) over holding Worker handles directly","Library-level fix: treat SendError as 'worker gone' - warn and drop the request (a dead worker already cancels everything it owned via runner.shutdown in runner.rs:572-669) instead of .expect","If you must call during teardown, wrap the call with AssertUnwindSafe(..).catch_unwind() and treat a panic as 'already cancelled/shutdown'"],"exampleFix":"// before (worker/mod.rs:164-167)\nself.msgs_tx\n    .send(WorkerMessage::CancelNotRunningTask { task_id, ack })\n    .await\n    .expect(\"Worker channel closed trying to cancel a not running task\");\n\n// after\nif self\n    .msgs_tx\n    .send(WorkerMessage::CancelNotRunningTask { task_id, ack })\n    .await\n    .is_err()\n{\n    warn!(%task_id, worker_id = self.id, \"Worker channel closed; cancel request dropped (worker already shutdown)\");\n}","handlingStrategy":"validation","validationCode":"// Same gate as for pause: block cancels once shutdown has started.\nuse std::sync::atomic::{AtomicBool, Ordering};\n\nstatic SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);\n\nfn can_control_tasks() -> bool { !SHUTTING_DOWN.load(Ordering::Acquire) }\n\n// if can_control_tasks() { system_comm.cancel_not_running_task(...).await }","typeGuard":null,"tryCatchPattern":"use futures::FutureExt;\nuse std::panic::AssertUnwindSafe;\n\nif AssertUnwindSafe(handle.cancel()).catch_unwind().await.is_err() {\n    // Cancel raced worker shutdown; shutdown finalizes/cancels everything\n    // the dead worker owned, so there is nothing left to cancel.\n}","preventionTips":["Await cancels (via the task's done/status channel) before starting shutdown, especially force-cancel flows triggered by user UI during teardown","Keep cancel logic off timers/watchdogs that can fire concurrently with system.shutdown()","Rely on system-level cancel, which returns tasks to the dispatcher on shutdown instead of needing a live worker"],"tags":["rust","tokio","async","panic","channel","shutdown","race-condition","task-system"],"backgroundTag":null,"analyzedSha":"6dfeccf2113039e35f2ce735f945e70dc3e4ea45","analyzedAt":"2026-08-16T11:26:17.074Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}