Skip to content

fix(audio-transform): serialize WebSocket writes to avoid concurrent-write panic - #10857

Merged
richiejp merged 2 commits into
mudler:masterfrom
Anai-Guo:fix/audio-transform-ws-concurrent-write
Jul 16, 2026
Merged

fix(audio-transform): serialize WebSocket writes to avoid concurrent-write panic#10857
richiejp merged 2 commits into
mudler:masterfrom
Anai-Guo:fix/audio-transform-ws-concurrent-write

Conversation

@Anai-Guo

Copy link
Copy Markdown
Contributor

What

AudioTransformStreamEndpoint writes to the same Gorilla WebSocket connection from two goroutines:

  • the backend-forwarding goroutine emits binary PCM frames (ws.WriteMessage) and can call sendWSError on a backend recv error;
  • the read loop calls sendWSError for malformed mid-stream JSON or a backend send failure.

Gorilla WebSocket permits only one concurrent writer. These two writers race, which can panic with concurrent write to websocket connection and reset the active client session. A -race build reports the data race directly.

Fix

Wrap the connection in a lockedConn that serializes WriteMessage behind a mutex — the same pattern already used by the openresponses WebSocket endpoint (core/http/endpoints/openresponses/websocket.go). Reads stay on the single read loop, so only writes need the lock. All existing ws. call sites are unchanged because lockedConn embeds *websocket.Conn (promoting ReadMessage/SetReadLimit/Close) and overrides only WriteMessage.

Fixes #10844

Notes

AI-assisted, human reviewed.

🤖 Generated with Claude Code

…write panic

AudioTransformStreamEndpoint writes to the same Gorilla WebSocket connection
from two goroutines: the backend-forwarding goroutine emits binary PCM frames
(and can call sendWSError on a backend recv error), while the read loop calls
sendWSError for malformed mid-stream JSON or a backend send failure. Gorilla
WebSocket permits only one concurrent writer, so these writers race and can
panic with "concurrent write to websocket connection", resetting the client
session; a -race build reports the data race directly.

Wrap the connection in a lockedConn that serializes WriteMessage behind a
mutex, mirroring the existing lockedConn used by the openresponses WebSocket
endpoint. Reads stay on the single read loop, so only writes need the lock.

Fixes mudler#10844

Signed-off-by: Tai An <antai12232931@outlook.com>
@mudler
mudler requested a review from richiejp July 16, 2026 06:55

@richiejp richiejp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Signed-off-by: Anai-Guo <antai12232931@anaiguo.com>
@localai-bot

Copy link
Copy Markdown
Collaborator

Reviewed this against issue #10844. Verification done:

  • Audited every write site on the WS connection in audio_transform.go: the backend-forwarding goroutine's ws.WriteMessage (binary PCM) and sendWSError (called from both the read loop and the goroutine's recv-error path) are the only writers, and both now go through lockedConn.WriteMessage under the mutex. Changing sendWSError to take *lockedConn makes bypassing the wrapper a compile error.
  • No WriteJSON/NextWriter/ping tickers in this endpoint; gorilla's implicit pong reply and Close use WriteControl/Close, which are documented safe to call concurrently with WriteMessage, so nothing escapes the lock.
  • Mutex lives in the wrapper and is only ever used via pointer (no lock copying), and it is held only for the duration of a single write, so no deadlock risk. Reads stay on the single read loop as before.
  • Pattern matches the existing lockedConn in core/http/endpoints/openresponses/websocket.go, so it is consistent with how the repo already solves this.

CI is green and the branch is mergeable. @mudler this looks good to merge: it serializes all WS writes exactly at the two racing sites from the issue, with a compile-enforced wrapper and no behavior change otherwise.

@richiejp
richiejp merged commit dc2cc4d into mudler:master Jul 16, 2026
65 checks passed
@localai-bot localai-bot added the bug Something isn't working label Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug 4: Concurrent WebSocket writes in audio transform trigger panics and data races

3 participants