mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-06 10:58:17 -05:00
[PR #24302] [CLOSED] fix: prevent STT from blocking the uvicorn event loop #66447
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24302
Author: @gaurav0107
Created: 5/1/2026
Status: ❌ Closed
Base:
dev← Head:fix/24169-stt-blocks-event-loop📝 Commits (1)
49fc6fdfix: prevent STT from blocking the uvicorn event loop📊 Changes
2 files changed (+4 additions, -3 deletions)
View changed files
📝
backend/open_webui/routers/audio.py(+3 -2)📝
backend/open_webui/routers/files.py(+1 -1)📄 Description
Description
Fixes #24169.
When a user uploads audio to
POST /api/v1/audio/transcriptions, the whole Open WebUI server becomes unresponsive until transcription finishes. Other users seeECONNREFUSEDand dropped Socket.IO WebSockets.Root cause (already diagnosed in detail by @Classic298 on #24169):
backend/open_webui/routers/audio.py::transcriptionisasync defbutfile.file.read(), andtranscribe(...)directly.transcribe(...)uses its ownThreadPoolExecutorand blocks the calling thread onfuture.result()until every chunk is transcribed by faster-whisper.With the default
UVICORN_WORKERS=1, one event loop serves both HTTP and Socket.IO, so a multi-minute CPU transcription stalls every other user.The exact same anti-pattern exists in
backend/open_webui/routers/files.py::process_uploaded_file._process_handler, which also callstranscribe(...)synchronously from anasyncfunction. It is fixed in the same PR.What this PR does
backend/open_webui/routers/audio.py:import asyncio.contents = file.file.read()withcontents = await file.read()(FastAPI's asyncUploadFile.read()).transcribe(...)inawait asyncio.to_thread(...).backend/open_webui/routers/files.py:transcribe(...)call inawait asyncio.to_thread(...).asynciois already imported, and the preceding line already uses the sameasyncio.to_threadpattern forStorage.get_file.transcribe()itself is untouched — it is a sync function that correctly uses aThreadPoolExecutorfor chunk parallelism. The fix is just to stop running it on the event loop.Net diff: +4 / -3 across 2 files, no new tests, no new dependencies, no env-var changes, no public-API changes.
Before / after (manual verification reasoning)
Before: a single STT request holds the sole uvicorn event loop until faster-whisper finishes; a concurrent
GET /or Socket.IO frame is queued behind the transcription and times out. Reporter's log shows ~18s of inference for 5s of audio; a 30s clip produces ~2 minutes of server freeze.After:
await file.read()releases the loop during upload I/O;await asyncio.to_thread(transcribe, ...)schedules the CPU-bound work on the default thread pool so the event loop stays free for chat streaming, WebSocket heartbeats, and other users.ENABLE_WEBSOCKET_SUPPORT=falseis no longer needed as a workaround.Testing
What I have verified:
ruff format --checkpasses on both modified files (the repo's only blocking backend CI check).transcribe()in the backend: exactly two call sites exist (audio.py::transcriptionandfiles.py::process_uploaded_file._process_handler), both fixed here.UploadFile.read()returns the samebytesas the rawSpooledTemporaryFile.read()underneathfile.file, andawait asyncio.to_thread(fn, *args)invokesfn(*args)on the default thread pool and returns its return value unchanged.await asyncio.to_thread(...)is already used on the preceding line offiles.py::_process_handler(forStorage.get_file), so this fix follows an idiom already in the file.What I have NOT personally verified (please gate on this):
Changelog
Checklist
dev.asyncio.to_threadhas been available since Python 3.9; the project requires >=3.11.asyncio.to_threadpattern already used on the preceding line offiles.py.transcribe().dev.fix:.Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.