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
- 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).
- Avoid sharing the `Project`/`ProjectCompiler` across threads, or serialise access so a panic in one compile cannot poison the lock for others.
- If recovery is acceptable, patch the call site to reuse the poisoned guard: `.lock().unwrap_or_else(|p| p.into_inner())` instead of `.expect(...)`.
- 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
- Do not share one Project/ProjectCompiler across multiple threads without synchronisation.
- Fix root-cause panics first — this error is always secondary to an earlier panic.
- Wrap each compilation task in catch_unwind so one failure cannot poison shared locks.
- Run CI builds with RUST_BACKTRACE=1 to surface the original panic quickly.
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)