JuliusBrussee/caveman · error · MiddlewareError

closed

closed

Error message

closed

What it means

MiddlewareRuntime.as_async() raises MiddlewareError("closed") when the sync runtime has already been shut down via close(). Once closed, the cached bounded async view can no longer be created or handed out. The library treats any use of a closed runtime as a programming error rather than silently recreating resources.

Solutions

  1. Obtain the async view via as_async() before closing the runtime, and keep the runtime alive as long as the async view is needed.
  2. Create a new MiddlewareRuntime instance instead of reusing the closed one.
  3. Guard call sites with a runtime.is-closed check or track lifecycle in application code before calling as_async().

Example fix

// before
view = runtime.as_async()  # after runtime.close() -> MiddlewareError: closed
// after
if not runtime._closed:  # or keep a single owner responsible for close
    view = runtime.as_async()
else:
    runtime = MiddlewareRuntime(...)
    view = runtime.as_async()
Defensive patterns

Strategy: try-catch

Validate before calling

def as_async_safe(rt):
    if getattr(rt, "_closed", False):
        return None
    return rt.as_async()

Type guard

def runtime_open(rt) -> bool:
    return not getattr(rt, "_closed", True)

Try / catch

try:
    view = rt.as_async()
except MiddlewareError as e:
    if str(e) == "closed":
        view = None
    else:
        raise

Prevention

When it happens

Trigger: Calling as_async() after MiddlewareRuntime.close() (or after exiting a `with` block, since __exit__ calls close()), or calling as_async() when the previously created async view was itself closed and the parent runtime is closed.

Common situations: Request handlers outliving application shutdown; context managers exited before background tasks fetch the async view; double-close race where one thread closes while another lazily builds the async view.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/6f5b9e882323f440. Report an issue: GitHub.

Appendix: source

Thrown at packages/sdk/python/caveman_cloud/middleware/runtime.py:99

        self._url = url
        self._connections: set[http.client.HTTPConnection] = set()
        self._caps: dict | None = None
        self._bindings: weakref.WeakKeyDictionary[RecoveryBinding, tuple] = weakref.WeakKeyDictionary()
        self._lock = threading.RLock()
        self._slots = threading.BoundedSemaphore(16)
        self._closed = False
        self._failures = 0
        self._open_until = 0.0
        self._receipt_pool: ThreadPoolExecutor | None = None
        self._receipt_slots = threading.BoundedSemaphore(16)
        self._async_view = None

    def as_async(self):
        """Cached bounded async view; close() releases its owned worker pool."""
        from .async_runtime import AsyncMiddlewareRuntime
        with self._lock:
            if self._closed:
                raise MiddlewareError("closed")
            if self._async_view is None or self._async_view._closed:
                self._async_view = AsyncMiddlewareRuntime.from_sync(self)
            return self._async_view

    def ready(self) -> dict:
        if self.mode == "off":
            raise MiddlewareError("off")
        value = validate.capabilities(self._http("capabilities", None, self.deadline_ms / 1000))
        with self._lock:
            if self._closed:
                raise MiddlewareError("closed")
            self._caps = value
        return copy.deepcopy(value)

    def preflight(self) -> PreflightReport:
        """Nonthrowing discovery, even in strict mode; sends no candidate content."""
        if self.mode == "off":
            return _preflight_report(self.mode, "disabled")

View on GitHub (pinned to 3ee70a1026)