sgl-project/sglang · error · RuntimeError

cannot force-build {python_module} after it has been importe

Error message

cannot force-build {python_module} after it has been imported; start a new Python process

What it means

In 'force' mode the loader must rebuild and re-import the extension, but Python cannot cleanly replace an already-imported native module. If the module is already in sys.modules, force-building is refused with this RuntimeError; the only remedy is a fresh process.

Source

Thrown at python/sglang/srt/rust_extensions/loader.py:97

    ``auto`` prefers a module bundled in the installed wheel, then a cached
    local build, and finally Cargo. ``never`` permits the first two but never
    invokes Cargo. ``force`` rebuilds from source and replaces the cache entry.
    ``mode`` defaults to ``SGLANG_RUST_BUILD_MODE``.
    """
    if mode is None:
        mode = envs.SGLANG_RUST_BUILD_MODE.get()
    if mode not in ("auto", "never", "force"):
        raise ValueError(
            f"invalid Rust extension build mode {mode!r}; expected auto, never, or force"
        )

    if mode != "force":
        module = _import_bundled_extension(python_module)
        if module is not None:
            return module
    elif python_module in sys.modules:
        raise RuntimeError(
            f"cannot force-build {python_module} after it has been imported; "
            "start a new Python process"
        )

    if workspace is None:
        workspace = _RUST_WORKSPACE
    crate = _discover_crate(workspace, python_module)
    context = _build_context(crate)
    cache_root = _cache_root(cache_dir)
    extension_path = _cached_extension_path(cache_root, crate, context.fingerprint)
    lock_path = (
        cache_root / "locks" / f"{crate.package}-{context.target_fingerprint}.lock"
    )

    with _filesystem_lock(lock_path):
        if mode != "force" and extension_path.is_file():
            return _load_extension_from_path(crate.python_module, extension_path)

View on GitHub (pinned to 0132848349)

Solutions

  1. Run the force build in a fresh Python process (subprocess or new test session) so the rebuilt cache is produced outside your interpreter
  2. Use force at most once per module per process — the first load in a process works; subsequent force loads fail
  3. If you just need the existing build, call with the default/auto mode, which returns the already-imported or cached module

Example fix

# before
mod = load_rust_extension(crate, mode="force")  # second call in same process
# after
import subprocess, sys
subprocess.run([sys.executable, "-c", "from sglang.srt.rust_extensions.loader import load_rust_extension; load_rust_extension(CRATE, mode='force')"], check=True)
mod = load_rust_extension(CRATE)  # auto: picks up the fresh cache
Defensive patterns

Strategy: fallback

Validate before calling

import sys
mode = "force" if crate.python_module not in sys.modules else "auto"
module = load_rust_extension(crate, mode=mode)

Try / catch

try:
    module = load_rust_extension(crate, mode="force")
except RuntimeError:
    module = load_rust_extension(crate)  # fall back to cached/imported build

Prevention

When it happens

Trigger: Calling load_rust_extension(mode="force") after anything (e.g. an earlier auto-mode load) already imported the crate — the loader checks python_module in sys.modules and raises; long-lived test sessions that try to force-rebuild between tests.

Common situations: Dev loops that edit Rust source and re-run the loader in the same interpreter (notebooks, a single pytest session); wrappers that unconditionally pass force on every call.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/a1506f4398e73051. Report an issue: GitHub.