gleam-lang/gleam · critical

could not lock beam_compiler

Error message

could not lock beam_compiler

What it means

This is a panic from `Mutex::lock()` inside `initialise_beam_compiler`: the mutex guarding the project's lazy `BeamCompilerInstance` was poisoned because another thread panicked while holding it. The library deliberately aborts rather than continue with a possibly half-initialised compiler. It is almost always a secondary error masking an earlier panic in a parallel build.

Solutions

  1. Find and fix the ORIGINAL panic: look earlier in the logs for the panic that poisoned the lock (the real error is upstream of this message).
  2. Avoid sharing the `Project`/`ProjectCompiler` across threads, or serialise access so a panic in one compile cannot poison the lock for others.
  3. If recovery is acceptable, patch the call site to reuse the poisoned guard: `.lock().unwrap_or_else(|p| p.into_inner())` instead of `.expect(...)`.
  4. Run `gleam` with `RUST_BACKTRACE=1` on the failing invocation to identify the thread that first panicked.

Example fix

// before
let mut guard = self.beam_compiler.lock().expect("could not lock beam_compiler");
// after (recover from a poisoned lock instead of panicking)
let mut guard = self
    .beam_compiler
    .lock()
    .unwrap_or_else(|poisoned| poisoned.into_inner());
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the project isn't contended before initialising
assert!( Arc::strong_count(&project_shared) <= 1, "project shared across threads" );

Type guard

fn lock_is_healthy<T>(m: &Mutex<T>) -> bool { !m.is_poisoned() }

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| project.initialise_beam_compiler()))
    .map_err(|_| anyhow!("beam compiler lock poisoned; an earlier thread panicked"))?

Prevention

When it happens

Trigger: Calling `project.initialise_beam_compiler()` (directly or via commands like `gleam compile-package`) while another thread already panicked inside a critical section guarded by `self.beam_compiler` — e.g. a concurrent `compile_beam` call crashed while the compiler instance was being created or used.

Common situations: Parallel/actor-based builds that share one `Project` across threads (as gleam's build engine does); a segfaulting or panicking beam compiler subprocess handler from a prior run in the same process; running gleam compilation inside a test harness that spawns many builds concurrently and one of them hits an unrelated bug first.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of gleam-lang/gleam@15b07c7830 (2026-09-14). Data as JSON: /api/errors/86f7203b5a95ec8c. Report an issue: GitHub.

Appendix: source

Thrown at compiler-cli/src/fs.rs:111

    beam_compiler: Arc<Mutex<Option<BeamCompilerInstance>>>,
}

impl ProjectIO {
    pub fn new() -> Self {
        Self {
            beam_compiler: Arc::new(Mutex::new(None)),
        }
    }

    pub fn boxed() -> Box<Self> {
        Box::new(Self::new())
    }

    pub(crate) fn initialise_beam_compiler(&self) -> Result<(), Error> {
        let mut guard = self
            .beam_compiler
            .lock()
            .expect("could not lock beam_compiler");
        if guard.is_none() {
            *guard = Some(BeamCompilerInstance::new(self)?);
        }
        Ok(())
    }
}

impl FileSystemReader for ProjectIO {
    fn read(&self, path: &Utf8Path) -> Result<String, Error> {
        read(path)
    }

    fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error> {
        read_bytes(path)
    }

    fn is_file(&self, path: &Utf8Path) -> bool {
        path.is_file()

View on GitHub (pinned to 15b07c7830)