Skip to content

fix(backend): offload listen WebSocket connect-time blocking reads (#9239)#9393

Open
kodjima33 wants to merge 1 commit into
mainfrom
issues-improver/9239-listen-connect-offload
Open

fix(backend): offload listen WebSocket connect-time blocking reads (#9239)#9393
kodjima33 wants to merge 1 commit into
mainfrom
issues-improver/9239-listen-connect-offload

Conversation

@kodjima33

@kodjima33 kodjima33 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

The /listen WebSocket handler (_stream_handler in backend/routers/transcribe.py) performed two blocking reads directly on the event loop at connect time:

  • user_db.get_user_private_cloud_sync_enabled(uid) — a synchronous Firestore .get() (database/users.py)
  • get_user_has_speech_profile(uid) — a synchronous GCS blob .exists() check (utils/other/storage.py)

Both are now routed through an awaited run_blocking(...) on the appropriate pool (db_executor for Firestore, storage_executor for GCS).

Why

From the issue (#9239):

The listen WebSocket handler blocks the event loop on a synchronous Firestore preference read at connect time instead of offloading via run_blocking.

backend/AGENTS.md is explicit: "Never call sync DB/storage/file functions directly inside async def — wrap with await run_blocking(executor, func, args)." A slow Firestore/GCS read on the connect path blocks the event loop for every other live connection on that worker, not just the connecting user. The rest of the handler already follows this pattern (e.g. get_user_speaker_embedding, get_profile_audio_if_exists); these two connect-time reads were missed.

What I verified

  • Added backend/tests/unit/test_transcribe_connect_offload_async.py — an AST structural test (same pattern as the existing test_phone_twiml_async.py) asserting neither read is called directly and both are offloaded to the correct executor pool.
  • Regression check: the new test fails on pre-fix code (2/3 assertions fail) and passes after the fix (3/3).
  • python3 -m py_compile routers/transcribe.py — OK.
  • Ran tests/unit/test_transcribe_decisions.py + test_transcribe_connect_offload_async.py — pass. (Two unrelated pre-existing failures in test_listen_session_bootstrap.py reproduce on clean origin/main and are untouched by this change.)

Behavior is unchanged for users — the same values are computed; they are now read off the event loop.

Auto-generated from issue feedback by the mini issues-improver. Review before merge.

Review in cubic

…9239)

_stream_handler read the user's private-cloud-sync preference
(user_db.get_user_private_cloud_sync_enabled, a synchronous Firestore
call) and speech-profile presence (get_user_has_speech_profile, a
synchronous GCS blob existence check) directly on the event loop at
connect time. Per backend/AGENTS.md, sync DB/storage functions in an
async def must be offloaded via run_blocking, or a slow read blocks the
event loop for every other live connection.

Route both through awaited run_blocking(db_executor/storage_executor,
...). Adds an AST structural regression test asserting neither is
called directly and both are offloaded to the correct pool.

fixes #9239
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