{"record":{"id":"f8381f3d168b0f68","repo":"spacedriveapp/spacedrive","slug":"worker-channel-closed-trying-to-pause-a-not-runnin","errorCode":null,"errorMessage":"Worker channel closed trying to pause a not running task","messagePattern":"Worker channel closed trying to pause a not running task","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/task-system/src/worker/mod.rs","lineNumber":156,"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::ResumeTask { task_id, ack })\n\t\t\t.await\n\t\t\t.expect(\"Worker channel closed trying to resume task\");\n\t}\n\n\tpub async fn pause_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::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) {","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/crates/task-system/src/worker/mod.rs#L138-L174","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before (worker/mod.rs:153-156)\nself.msgs_tx\n    .send(WorkerMessage::PauseNotRunningTask { task_id, ack })\n    .await\n    .expect(\"Worker channel closed trying to pause a not running task\");\n\n// after\nif self\n    .msgs_tx\n    .send(WorkerMessage::PauseNotRunningTask { task_id, ack })\n    .await\n    .is_err()\n{\n    warn!(%task_id, worker_id = self.id, \"Worker channel closed; pause request dropped (worker already shutdown)\");\n}","handlingStrategy":"validation","validationCode":"// Gate all task-control calls (pause/cancel/abort) with a shutdown flag you set\n// BEFORE calling system.shutdown(); the library's own has_shutdown check\n// (system.rs:629/653) cannot see requests already in flight.\nuse std::sync::atomic::{AtomicBool, Ordering};\n\nstatic SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);\n\nfn set_shutting_down() { SHUTTING_DOWN.store(true, Ordering::Release); }\n\nfn can_control_tasks() -> bool { !SHUTTING_DOWN.load(Ordering::Acquire) }\n\n// usage:\n// set_shutting_down();\n// system.shutdown().await;","typeGuard":null,"tryCatchPattern":"// Rust has no try/catch; a panic can only be contained via catch_unwind.\n// Use only as a boundary around the control call, never as normal flow.\nuse futures::FutureExt;\nuse std::panic::AssertUnwindSafe;\n\nlet result = AssertUnwindSafe(handle.pause()).catch_unwind().await;\nmatch result {\n    Ok(_) => {}\n    Err(_) => {\n        // Worker channel closed: the pause raced worker shutdown.\n        // Treat the task as shutdown-suspended; shutdown finalizes all tasks anyway.\n    }\n}","preventionTips":["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"],"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"}