diff --git a/docs/design/fullduplex.md b/docs/design/fullduplex.md index 324cd5bdc..df698880e 100644 --- a/docs/design/fullduplex.md +++ b/docs/design/fullduplex.md @@ -286,8 +286,11 @@ output DuplexOrchestrator._intercept_stage_output -> runner.on_stage_output -> detach DuplexOmni.detach_session -> touch(DETACH): engine-owned disconnect grace; expiry -> SessionExpired resume DuplexOmni.resume_session(expected_lease_generation) -> lease CAS; the existing handle is re-entered close DuplexOmni.close_session -> close RPC; the manager tears the runner down, then the stage cleanup - (abort submitted requests, release reserved ids), then SessionClosed, then the RPC result: seeing - the event means the admission slot is free (it is held until the cleanup succeeded) + (abort submitted requests, release reserved ids), then SessionClosed, then the RPC result. + SessionClosed is emitted after the cleanup attempt (in a finally), so it is also sent when the + stage cleanup failed; in that case the admission slot is intentionally retained and the cleanup + is retried by the reaper, and opening a replacement session can still be refused with + resource_exhausted. Seeing the event therefore does not by itself guarantee a free slot. reap DuplexSessionManager.reaper_loop: idle TTL / disconnect grace expiry, cleanup retries ``` diff --git a/vllm_omni/engine/duplex/session/manager.py b/vllm_omni/engine/duplex/session/manager.py index 15e038806..605ed8eba 100644 --- a/vllm_omni/engine/duplex/session/manager.py +++ b/vllm_omni/engine/duplex/session/manager.py @@ -604,9 +604,12 @@ class DuplexSessionManager: The session keeps its admission slot (``_closing``) until stage cleanup succeeded; a failed cleanup is retried by the reaper. For an explicit close the ``session.closed`` event is emitted only after the cleanup - attempt, so a client that sees it can open a replacement session at - once (the runner used to emit it before the stage requests were - aborted, which let a prompt reopen hit ``resource_exhausted``). + attempt, in a ``finally``, including when the cleanup failed (the + runner used to emit it before the stage requests were aborted). A + client that sees the event can normally open a replacement session + right away, but after a failed cleanup the slot is still held until + the reaper succeeds, so a prompt reopen can be refused with + ``resource_exhausted``. """ session = runner.session submitted = tuple(session.resource_request_ids(submitted=True))