Skip to content

server: release the session lock when a client disconnects mid-stream - #104

Open
jethac wants to merge 1 commit into
NVIDIA:mainfrom
jethac:fix-session-lock-on-disconnect
Open

server: release the session lock when a client disconnects mid-stream#104
jethac wants to merge 1 commit into
NVIDIA:mainfrom
jethac:fix-session-lock-on-disconnect

Conversation

@jethac

@jethac jethac commented Aug 3, 2026

Copy link
Copy Markdown

Problem

opus_reader.read_pcm() returns None once the client's opus stream ends — which happens whenever a client goes away mid-session rather than closing cleanly. opus_loop then evaluates pcm.shape[-1] on None and raises inside the async with self.lock block, so the task dies while still holding the session lock.

What makes this awkward to diagnose is that the server does not crash. It keeps serving HTTP and accepting websocket upgrades, but every subsequent /api/chat blocks forever waiting for a lock that will never be released. Only a restart clears it.

Task exception was never retrieved
future: <Task finished coro=<ServerState.handle_chat.<locals>.opus_loop() ...>
    exception=AttributeError("'NoneType' object has no attribute 'shape'")>
  File ".../moshi/server.py", line 212, in opus_loop
    if pcm.shape[-1] == 0:
       ^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'shape'

Reproduction

  1. Connect a client and start streaming audio.
  2. Kill it without closing the socket (drop the process, pull the network).
  3. Connect again — the new session hangs indefinitely.

Hit this in practice with a robot client on flaky wifi; it also shows up any time a browser tab is closed mid-conversation.

Fix

Treat None as the stream-ended signal it is and return, which unwinds the context manager and frees the lock for the next session. Four lines, no behaviour change on the normal path.

Independent of any hardware or quantization work — applies to main as-is.

`opus_reader.read_pcm()` returns None once the client's opus stream ends,
which happens whenever a client goes away mid-session rather than closing
cleanly. `opus_loop` then evaluates `pcm.shape[-1]` on None and raises
inside the `async with self.lock` block, so the task dies while still
holding the session lock.

The server does not crash, which is what makes this awkward to diagnose: it
keeps serving HTTP and accepting websockets, but every subsequent
/api/chat blocks forever waiting for a lock that will never be released.
Only a restart clears it.

Repro: connect a client, start streaming audio, kill it without closing the
socket, then try to connect again.

Treat the None as the stream-ended signal it is and return, which unwinds
the context manager and frees the lock for the next session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant