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 section

View on GitHub (pinned to 61d3082821)

Solutions

  1. Fix the original panic that poisoned the imageproc mutex (look for an earlier panic backtrace in the logs).
  2. Use lock().unwrap_or_else(|p| p.into_inner()) to recover from poisoning if the data is still consistent.
  3. Ensure no code path calls set_base_url after a worker thread has failed.
  4. 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

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


AI-assisted analysis of getzola/zola@61d3082821 (2026-09-03). Data as JSON: /api/errors/1ac22e6f5a75f7ff. Report an issue: GitHub.