getzola/zola · error
Couldn't lock imageproc (set_base_url)
Error message
Couldn't lock imageproc (set_base_url)
What it means
Site::set_base_url locks the Site's imageproc Mutex (a std::sync::Mutex guard shared with other threads, e.g. the template functions) and panics with this expect message if the lock is unavailable. A std Mutex only fails when the mutex is poisoned — i.e. another thread panicked while holding it — or on unrecoverable locking errors.
Source
Thrown at components/site/src/lib.rs:174
self.live_reload = get_available_port(interface, port_to_avoid);
}
/// Only used in `zola serve` to re-use the initial websocket port
pub fn enable_live_reload_with_port(&mut self, live_reload_port: u16) {
self.live_reload = Some(live_reload_port);
}
/// Reloads the templates and rebuild the site without re-markdown the Markdown.
pub fn reload_templates(&mut self) -> Result<()> {
self.tera = load_tera(&self.base_path, &self.config)?;
tpls::register_early_global_fns(self);
// TODO: be smarter than that, no need to recompile sass for example
self.build()
}
pub fn set_base_url(&mut self, base_url: String) {
self.config.base_url = base_url;
let mut imageproc = self.imageproc.lock().expect("Couldn't lock imageproc (set_base_url)");
imageproc.set_base_url(&self.config);
}
pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) {
self.output_path = path.as_ref().to_path_buf();
}
pub fn minify(&mut self) {
self.config.minify_html = true;
}
/// Reads all .md files in the `content` directory and create pages/sections
/// out of them
pub fn load(&mut self) -> Result<()> {
self.library = Arc::new(Library::new(&self.config));
// not the most elegant loop, but this is necessary to use skip_current_dir
// which we can only decide to use after we've deserialised the sectionView on GitHub (pinned to 61d3082821)
Solutions
- Fix the original panic that poisoned the imageproc mutex (look for an earlier panic backtrace in the logs).
- Use lock().unwrap_or_else(|p| p.into_inner()) to recover from poisoning if the data is still consistent.
- Ensure no code path calls set_base_url after a worker thread has failed.
- If re-entrancy is possible (set_base_url called from within image processing), restructure to avoid nested locking.
Example fix
// before
let mut imageproc = self.imageproc.lock().expect("Couldn't lock imageproc (set_base_url)");
// after
let mut imageproc = self.imageproc.lock().unwrap_or_else(|p| p.into_inner()); Defensive patterns
Strategy: try-catch
Try / catch
// Recover from a poisoned mutex instead of panicking
let mut imageproc = match site.imageproc.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(), // log and recover
}; Prevention
- Fix any earlier image-processing panic before calling set_base_url again.
- Restart the process after a panic rather than reusing the poisoned Site.
- Avoid nested/re-entrant calls that hold the imageproc lock.
- Prefer unwrap_or_else(|p| p.into_inner()) at lock sites where state stays valid.
When it happens
Trigger: Calling set_base_url after any thread holding the imageproc lock has panicked (poisoned mutex). With ReentrantLock-like re-entrancy in older code, also calling it from a thread that already holds the lock.
Common situations: A panic inside image processing (e.g. in get_image_metadata or resize_image template functions) poisons the mutex; a later set_base_url call then panics with this message.
Related errors
- Couldn't lock imageproc (num_img_ops)
- result cache lock
- Couldn't lock imageproc (process_images)
- result cache lock
- response client lock
AI-assisted analysis of getzola/zola@61d3082821 (2026-09-03).
Data as JSON: /api/errors/1ac22e6f5a75f7ff.
Report an issue: GitHub.