{"record":{"id":"a243804e6208d623","repo":"GitoxideLabs/gitoxide","slug":"time-travel-worker-stopped-unexpectedly","errorCode":null,"errorMessage":"time-travel worker stopped unexpectedly","messagePattern":"time-travel worker stopped unexpectedly","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/lib.rs","lineNumber":5749,"sourceCode":"            let _ = sender.send(TravelWorkerEvent::Complete(result));\n        });\n        let mut last_draw: Option<Instant> = None;\n        let mut latest = None;\n        let mut rendered = None;\n        let mut complete = None;\n        let mut render_error = None;\n        let result = loop {\n            let event = if complete.is_some() {\n                None\n            } else if render_error.is_none()\n                && latest != rendered\n                && let Some(last_draw) = last_draw\n            {\n                match receiver.recv_timeout(FRAME_INTERVAL.saturating_sub(last_draw.elapsed())) {\n                    Ok(event) => Some(event),\n                    Err(mpsc::RecvTimeoutError::Timeout) => None,\n                    Err(mpsc::RecvTimeoutError::Disconnected) => {\n                        break Err(anyhow::anyhow!(\"time-travel worker stopped unexpectedly\"));\n                    }\n                }\n            } else {\n                match receiver.recv() {\n                    Ok(event) => Some(event),\n                    Err(_) => break Err(anyhow::anyhow!(\"time-travel worker stopped unexpectedly\")),\n                }\n            };\n            match event {\n                Some(TravelWorkerEvent::Rebased(id)) => latest = Some(id),\n                Some(TravelWorkerEvent::Complete(result)) => complete = Some(result),\n                None => {}\n            }\n            if render_error.is_none()\n                && latest != rendered\n                && (last_draw.is_none()\n                    || last_draw.is_some_and(|last_draw| last_draw.elapsed() >= FRAME_INTERVAL)\n                    || complete.is_some())","sourceCodeStart":5731,"sourceCodeEnd":5767,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/lib.rs#L5731-L5767","documentation":"The UI event loop receives events from a time-travel worker thread over an mpsc channel. When `recv_timeout` returns `RecvTimeoutError::Disconnected`, the worker's sending half was dropped — meaning the background worker terminated without sending a `Complete` result. The loop breaks with this error.","triggerScenarios":"`receiver.recv_timeout(...)` returns `Disconnected` because the time-travel worker thread dropped its `Sender` (worker finished its function early, returned without sending, or its channel sender was closed).","commonSituations":"Worker thread exiting early due to an error path that doesn't send a terminal event; worker thread killed by a panic whose JoinHandle isn't checked yet; premature drop of the worker sender during shutdown races.","solutions":["Audit the worker loop so every exit path sends a `TravelWorkerEvent::Complete(result)` before dropping the sender.","Check `worker.join()` result and surface the worker's actual panic/error instead of the generic message.","Reproduce the premature worker exit with tracing enabled to find the early-return path."],"exampleFix":"// before\nErr(mpsc::RecvTimeoutError::Disconnected) => {\n    break Err(anyhow::anyhow!(\"time-travel worker stopped unexpectedly\"));\n}\n// after\nErr(mpsc::RecvTimeoutError::Disconnected) => {\n    let worker_err = worker.join().err().map(|_| \"worker panicked\");\n    break Err(anyhow::anyhow!(\"time-travel worker stopped unexpectedly: {worker_err:?}\"));\n}","handlingStrategy":"try-catch","validationCode":"if worker.is_finished() {\n    eprintln!(\"time-travel worker already exited; skipping event wait\");\n    return;\n}","typeGuard":"fn worker_alive(worker: &std::thread::JoinHandle<()>) -> bool { !worker.is_finished() }","tryCatchPattern":"match receiver.recv_timeout(dur) {\n    Ok(ev) => handle(ev),\n    Err(mpsc::RecvTimeoutError::Timeout) => {},\n    Err(mpsc::RecvTimeoutError::Disconnected) => {\n        let cause = worker.join().err();\n        break Err(anyhow!(\"time-travel worker stopped: {cause:?}\"));\n    }\n}","preventionTips":["Ensure worker threads always send a terminal event before returning.","Wrap worker bodies so panics still deliver an error event over the channel.","Keep the channel sender alive until the terminal event is sent."],"tags":["channel","worker-thread","mpsc","ui"],"backgroundTag":"worker-thread-disconnected","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}