You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Local chat model's Metal/GPU resources are never released on quit, reproducing the SIGABRT-on-quit crash class for chat sessions #2952
Any user who loads a local/agent chat model — the default config (ChatConfig::default().n_gpu_layers = GPU_OFFLOAD_ALL_LAYERS) fully offloads it to Metal — and then quits the app through the normal Quit path (NSApplication terminate: → exit(), or the tray's ControlFlow::Exit → process::exit(0)) will hit the same class of SIGABRT crash that previously took four dedicated fix commits (#864, #897, #885, #908) to eliminate for the embedding path. This is a real, previously-fixed-elsewhere crash class regressing on the chat/agent feature that was layered on afterward (#1025/#1042).
Root cause
register_atexit_handler() installs one process-wide libc::atexit callback shared by both services, per its own doc comment ("Shared between embedding and chat services since both load models into the same global llama.cpp backend"). But the cleanup() callback (packages/nlp-engine/src/embedding.rs:77-96) only iterates the embedding-only LLAMA_STATES registry before dropping the shared LLAMA_BACKEND. register_state_for_cleanup (line 104) is private and typed only for Arc<Mutex<Option<LlamaState>>> — it is structurally incompatible with ChatLlamaState, chat's distinct state type. ChatEngine::load_model (chat/mod.rs:290) only calls register_atexit_handler() to ensure the shared callback exists; it never registers self.state anywhere. Neither the daemon's graceful-shutdown GPU drain (drain_and_release_gpu, embedding-only) nor LocalAgentService::shutdown() (which only cancels tokens, never touches the underlying ChatEngine) release a loaded chat model either. So exit() skips ordinary Rust Drop, the atexit callback runs but never frees the chat model, and LLAMA_BACKEND gets torn down while ChatLlamaState's Metal residency sets are still live.
Suggested fix
Register ChatLlamaState in the same (or an analogous) cleanup registry used for the embedding path — either generalize LLAMA_STATES to hold both state types via a trait object/enum, or add a second CHAT_LLAMA_STATES registry plus a cleanup loop that runs inside the same atexit callback before LLAMA_BACKEND is dropped, mirroring register_state_for_cleanup's call in embedding's load_model path.
Found via the 2026-09-25 full-repo code sweep (finding #2 of 84 in CODE_SWEEP_FINDINGS_2026-09-25.md). Severity: critical. Category: broken.
Activity
malibio commentedon Sep 25, 2026
Frontend: 265 files / 5293 tests passed