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

  1. Treat it as terminal: stop submitting work and unwind the caller gracefully.
  2. Await engine shutdown completion before spawning new calls.
  3. 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

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


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/ed2f90255e0b6630. Report an issue: GitHub.