{"record":{"id":"ec6a8d1ca2d597bd","repo":"emilk/egui","slug":"failed-to-spawn-thread","errorCode":null,"errorMessage":"failed to spawn thread","messagePattern":"failed to spawn thread","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/egui_extras/src/loaders/file_loader.rs","lineNumber":136,"sourceCode":"                            let mut cache = cache.lock();\n                            if let std::collections::hash_map::Entry::Occupied(mut entry) = cache.entry(uri.clone()) {\n                                let entry = entry.get_mut();\n                                *entry = Poll::Ready(result);\n                                log::trace!(\"Finished loading {uri:?}\");\n                                true\n                            } else {\n                                log::trace!(\"Canceled loading {uri:?}\\nNote: This can happen if `forget_image` is called while the image is still loading.\");\n                                false\n                            }\n                        };\n                        // We may not lock Context while the cache lock is held (see ImageLoader::load\n                        // for details).\n                        if repaint {\n                            ctx.request_repaint();\n                        }\n                    }\n                })\n                .expect(\"failed to spawn thread\");\n\n            Ok(BytesPoll::Pending { size: None })\n        }\n    }\n\n    fn forget(&self, uri: &str) {\n        let _ = self.cache.lock().remove(uri);\n    }\n\n    fn forget_all(&self) {\n        self.cache.lock().clear();\n    }\n\n    fn byte_size(&self) -> usize {\n        self.cache\n            .lock()\n            .values()\n            .map(|entry| match entry {","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/emilk/egui/blob/441971a776322a482e371775219380eca812cfa9/crates/egui_extras/src/loaders/file_loader.rs#L118-L154","documentation":"egui_extras' file loader spawns a background OS thread to perform blocking file reads so load() can return BytesPoll::Pending immediately. It calls thread::Builder::spawn(...).expect(\"failed to spawn thread\"), which panics when the OS refuses to create a thread — usually because the process hit the thread or memory limit. Any egui app loading files via Image::from_uri on the file loader hits this path.","triggerScenarios":"Calling load() on the FileLoader when std::thread::Builder::spawn returns Err — the process has exhausted the OS thread limit (RLIMIT_NPROC / cgroup pids.max / threads-max), is out of virtual memory for a new stack, or is running in a restricted sandbox that forbids thread creation.","commonSituations":"CI or Docker containers with low pids limits; apps that leak loader threads (one per Image that never finishes loading, e.g. missing files or a spinning disk stall) until the limit is hit; embedded/minimal environments with strict resource cgroups; heavily loaded hosts at the kernel threads-max cap.","solutions":["Raise the process/thread limit (ulimit -u, Docker --pids-limit, cgroup pids.max) or free memory","Check the app for leaked/stuck loader threads — count them (e.g. /proc/<pid>/status Threads) and fix URIs that never resolve","Pin egui_extras to a version whose file loader reuses a thread pool or handles spawn failure gracefully, or implement a custom ImageLoader that returns Ok(BytesPoll::Pending) when spawn fails","Retry the load later once system pressure subsides"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before heavy image loading, check headroom for another thread:\nlet threads = std::fs::read_to_string(\"/proc/self/status\").ok()\n    .and_then(|s| s.lines().find(|l| l.starts_with(\"Threads:\"))?.split_whitespace().last()?.parse::<u32>().ok());\nif let Some(t) = threads { eprintln!(\"thread count: {t}\"); }","typeGuard":null,"tryCatchPattern":"// Wrap loading so a spawn failure degrades instead of panicking:\nfn poll_uri(ctx: &Context, uri: &str) -> BytesPoll {\n    match std::panic::catch_unwind(|| loader.load(ctx, uri)) {\n        Ok(poll) => poll,\n        Err(_) => BytesPoll::Pending { size: None }, // retry later\n    }\n}","preventionTips":["Set generous --pids-limit / ulimit -u in containers running egui apps","Watch for loader thread leaks: every Image::from_uri that never resolves keeps a thread alive","Prefer a custom/pooled loader for apps that load many files concurrently","Retry loads with backoff under memory or thread pressure instead of spawning more"],"tags":["threading","resources","panic","loader"],"backgroundTag":"resource-exhaustion","analyzedSha":"441971a776322a482e371775219380eca812cfa9","analyzedAt":"2026-09-12T04:29:21.500Z","contentChangedAt":"2026-09-12T04:29:21.500Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}