BoundaryML/baml · error · EngineError
BAML engine is shutting down
Error message
BAML engine is shutting down
What it means
`EngineError::ShuttingDown` is returned when an operation is attempted on the BAML (bex) engine while it is shutting down. Once shutdown begins, the engine stops accepting new calls, futures, or lookups and reports this error to in-flight and new requests. It is a graceful-refusal signal, not a bug.
Source
Thrown at baml_language/crates/bex_engine/src/lib.rs:798
// doesn't double-panic.
let mut map = self
.engine
.active_calls
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&self.call_id);
let no_active_calls = map.values().all(|call| call.pending);
drop(map);
if no_active_calls {
self.engine.lifecycle_changed.notify_waiters();
}
}
}
/// Errors that can occur during engine execution.
#[derive(Debug, PartialEq, Error, Clone)]
pub enum EngineError {
#[error("BAML engine is shutting down")]
ShuttingDown,
#[error("Function call with ID {call_id} not found")]
FunctionCallNotFound { call_id: CallId },
#[error("Future with ID {future_id} not found")]
FutureNotFound { future_id: FutureId },
#[error("Function not found: {name}")]
FunctionNotFound { name: String },
/// Function exists, but its `FunctionKind` is not invokable as an
/// engine entry point. Only bytecode functions can be called via
/// [`BexEngine::call_function`] / [`BexEngine::call_function_bound_args`]:
/// native (`$rust_function`) entries would re-enter the VM through
/// `YieldToCall` with no bytecode frame to return to. Sysops + builtins
/// reach their natives from inside a calling bytecode body.
#[error("Function `{name}` is not invokable as an entry point (kind: {kind})")]View on GitHub (pinned to bd85ce9dee)
Solutions
- Treat it as terminal: stop submitting work and unwind the caller gracefully.
- Await engine shutdown completion before spawning new calls.
- If the engine should still be alive, audit handle lifetime and reinitialize the engine before retrying.
Example fix
// before
let out = engine.call_function("f", args).await?;
// after
if !engine.is_running() { return; }
let out = match engine.call_function("f", args).await {
Err(EngineError::ShuttingDown) => return Ok(()),
other => other?,
}; Defensive patterns
Strategy: try-catch
Validate before calling
// rust
if !engine.is_running() { /* skip submission */ } Try / catch
// rust
match engine.call_function(name, args).await {
Err(EngineError::ShuttingDown) => /* graceful exit */,
other => other?,
} Prevention
- Coordinate engine shutdown with task lifecycles (join before drop)
- Use a readiness flag/cancellation token checked before submissions
- Avoid long-lived engine handles that outlive intentional shutdown
When it happens
Trigger: Calling into `BexEngine` (e.g. `call_function`, submitting work, or awaiting a future) after shutdown has been initiated; racing an engine stop with pending submissions.
Common situations: Application shutdown while background tasks still submit engine calls; a test harness dropping the engine while async tasks run; a client holding a stale engine handle after restart.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Function call with ID {call_id} not found
- Future with ID {future_id} not found
- Client closed
- engine is not current with the latest sources; wait for the
- tests have not been collected for this build yet
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/ed2f90255e0b6630.
Report an issue: GitHub.