{"record":{"id":"aa3a1d015109fc51","repo":"spacedriveapp/spacedrive","slug":"system-channel-closed-trying-to-report-working","errorCode":null,"errorMessage":"System channel closed trying to report working","messagePattern":"System channel closed trying to report working","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/task-system/src/system.rs","lineNumber":459,"sourceCode":"\t\tspawn(\n\t\t\tasync move {\n\t\t\t\tsystem_tx\n\t\t\t\t\t.send(SystemMessage::IdleReport(worker_id))\n\t\t\t\t\t.await\n\t\t\t\t\t.expect(\"System channel closed trying to report idle\");\n\t\t\t}\n\t\t\t.in_current_span(),\n\t\t);\n\t}\n\n\tpub fn working_report(&self, worker_id: usize) {\n\t\tlet system_tx = self.0.clone();\n\t\tspawn(\n\t\t\tasync move {\n\t\t\t\tsystem_tx\n\t\t\t\t\t.send(SystemMessage::WorkingReport(worker_id))\n\t\t\t\t\t.await\n\t\t\t\t\t.expect(\"System channel closed trying to report working\");\n\t\t\t}\n\t\t\t.in_current_span(),\n\t\t);\n\t}\n\n\tpub fn pause_not_running_task(\n\t\t&self,\n\t\ttask_id: TaskId,\n\t\ttask_work_table: Arc<TaskWorktable>,\n\t\tack: oneshot::Sender<Result<(), SystemError>>,\n\t) {\n\t\tlet system_tx = self.0.clone();\n\t\tspawn(\n\t\t\tasync move {\n\t\t\t\tsystem_tx\n\t\t\t\t\t.send(SystemMessage::PauseNotRunningTask {\n\t\t\t\t\t\ttask_id,\n\t\t\t\t\t\ttask_work_table,","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/crates/task-system/src/system.rs#L441-L477","documentation":"SystemComm::working_report (crates/task-system/src/system.rs:452) spawns a detached Tokio task that sends SystemMessage::WorkingReport into the system's bounded(8) message channel; the .expect at system.rs:459 panics when that channel is closed, i.e. every receiver is gone. The receiver is the system message loop spawned in System::new (system.rs:73-97), which exits only after System::shutdown's ShutdownRequest is processed or the Tokio runtime is torn down. The library treats 'system channel closed while a worker still reports' as an invariant violation, so it panics inside the detached task instead of returning an error.","triggerScenarios":"A worker reports it is working while System::shutdown() has already been processed (or races with it), so the system loop's receiver is dropped; or the Tokio runtime owning the System is dropped while workers are alive. No public API calls working_report directly; it fires from worker internals (idle/working transitions).","commonSituations":"Daemon or app exit paths that drop the Tokio runtime before quiescing the task system; #[tokio::test] tests that return while background workers still run; calling System::shutdown() concurrently with dispatch or control traffic.","solutions":["Quiesce first: await every TaskHandle, then system.shutdown().await to completion, and only then drop the runtime / end the test","Keep the System alive for the whole process inside one long-lived Tokio runtime instead of per-thread or short-lived runtimes","Avoid issuing dispatch/control operations concurrently with System::shutdown()","Library fix: add the same has_shutdown guard BaseDispatcher::dispatch_boxed uses (system.rs:629) to SystemComm methods, and log/return an error instead of .expect"],"exampleFix":"// before\nlet rt = tokio::runtime::Runtime::new().unwrap();\nlet system = rt.block_on(async { System::<MyErr>::new() });\ndrop(system);\ndrop(rt); // workers' spawned tasks still alive; working_report panics during teardown\n\n// after\nlet rt = tokio::runtime::Runtime::new().unwrap();\nrt.block_on(async {\n    let system = System::<MyErr>::new();\n    let handles = system.dispatch_many(tasks).await?;\n    let _ = futures_util::future::join_all(handles).await; // quiesce workers\n    system.shutdown().await; // message loop drains and exits cleanly\n});\ndrop(rt);","handlingStrategy":"validation","validationCode":"// Prove the system is quiesced BEFORE teardown drops the runtime.\nuse futures_util::future::join_all;\n\nlet handles = system.dispatch_many(tasks).await?; // or collect as you dispatch\nlet _ = join_all(handles).await;                   // 1. every worker idle, no reports in flight\nsystem.shutdown().await;                           // 2. message loop drains and exits\n// 3. only now may the #[tokio::test] end / the Runtime be dropped","typeGuard":null,"tryCatchPattern":"This panic occurs inside a detached Tokio task, so call-site catch_unwind cannot intercept it. Register a logging panic hook (std::panic::set_hook) to capture which task-system sender panicked, and correct the shutdown ordering instead.","preventionTips":["Host the System on one long-lived Tokio runtime; never create it on a runtime you drop early","Treat System::shutdown as a barrier: no dispatch or control calls until it completes","Await every TaskHandle before ending a #[tokio::test]","Run with RUST_LOG=task_system=trace so the last worker report before the panic is visible"],"tags":["rust","tokio","task-system","panic","channel","shutdown","lifecycle"],"backgroundTag":null,"analyzedSha":"6dfeccf2113039e35f2ce735f945e70dc3e4ea45","analyzedAt":"2026-08-16T11:26:17.074Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}