fix: async Deepgram retry + abort on client disconnect#5579
Conversation
…#5577) - Make connect_to_deepgram_with_backoff async, replacing time.sleep() with await asyncio.sleep() to prevent event loop starvation - Add is_active callback parameter to abort retries when client disconnects, matching the pattern from connect_to_trigger_pusher - Guard process_audio_dg against None return from aborted retries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#5577) Thread is_active=lambda: websocket_active to all process_audio_dg calls so Deepgram retries abort when the client disconnects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…5577) 6 tests covering: first-success return, async sleep retries, exhaustion raise, abort-before-first-attempt, abort-between-retries, and None is_active passthrough. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
PR ready for mergeAll checkpoints passed:
Awaiting explicit merge approval from manager. by AI for @beastoin |
CP9 Level 1: Local dev backend live test evidenceRan local backend on port 8790 with real Deepgram connections using Test 1: Basic streaming — PASSED
Test 2: Client disconnect (is_active abort) — PASSED
Test 3: Concurrent connections (5x) — PARTIAL
Key log evidenceLevel 2/3 statusLive test script ( by AI for @beastoin |
CP9 Level 1: Local Dev Backend Live Test — All Durations PASSEDTested with local dev backend on port 8790, stub pusher on 8791. Audio: looped silero-vad test.wav at 8kHz mono PCM16, streamed at real-time pace (100ms chunks) mimicking Flutter app behavior. Results
Key observations
Test script
by AI for @beastoin |
Mimics Flutter app behavior — streams WAV audio at real-time pace over WebSocket, tracks connection stability, transcript segments, and latency. Supports podcast, disconnect, and concurrent connection tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All Checkpoints Passed — Ready for Merge
PR is ready for merge. Awaiting explicit merge approval. by AI for @beastoin |
Backend sends transcript segments as a plain JSON array, not
{"segments": [...]}. The test was missing most transcripts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CP9 Level 1 Corrected — Fixed test script segment parsingPrevious test results showed only 20 words for 15m because the test script looked for Corrected results (with DG prerecorded cross-check)
Streaming word counts are higher because each incremental segment update includes cumulative text. DG prerecorded confirms the audio contains real speech content — the pipeline is working correctly end-to-end. by AI for @beastoin |
|
lgtm |
Summary
Fixes #5577 — Replace blocking
time.sleep()withawait asyncio.sleep()in Deepgram retry backoff, and addis_activecallback to abort retries when client disconnects.Root cause
connect_to_deepgram_with_backoffusedtime.sleep()which blocks the single-worker uvicorn event loop. During DG outages, 10 concurrent retries × 3-5s each = 30-50s stall, cascading all pod connections (Mar 11 incident amplifier).Changes
backend/utils/stt/streaming.py—connect_to_deepgram_with_backoffconverted toasync def,time.sleep()→await asyncio.sleep(), addedis_activeparameter to abort retries on client disconnectbackend/routers/transcribe.py— Passis_active=lambda: websocket_activeto all 3 DG connection call sites (multi-channel, main socket, speech profile socket)backend/tests/unit/test_streaming_deepgram_backoff.py— 10 unit tests covering: first-success, async-sleep-retries, exhaustion-raise, abort-before-first, abort-between-retries, is_active-none, retries=0, retries=1, process_audio_dg-returns-none, no-vad-wrap-on-nonebackend/test.sh— Added new test filebackend/scripts/test_live_streaming_backoff.py— Live streaming test script mimicking Flutter app behavior (podcast/disconnect/concurrent tests)Testing
Unit tests: 10/10 passing
CP9 Level 1 Live Tests (local dev backend, 8kHz mono PCM16 podcast audio):
Session ended, abortingRisks / edge cases
connect_to_deepgram_with_backoffreturnsNonewhenis_activeaborts — callers must handle this (guarded inprocess_audio_dg)is_activeparameter are unaffected (defaultNoneskips the check)Audit
No other blocking
time.sleep()in async streaming paths. Othertime.sleep()calls inchat.py,social.py,postprocess_conversation.pyare in synchronous/background-thread contexts.Review cycle