Before Submitting
Installation Method
Docker
Open WebUI Version
v0.11.3
Operating System
Debian 13
Summary
Web search with WEB_LOADER_ENGINE=playwright permanently spins a CPU core when a fetched page opens a WebSocket (sync ws_route.close() re-enters the dispatcher)
Every web-search page load that opens a WebSocket permanently loses one worker thread to a busy loop at 100% CPU. The thread never exits, holds the GIL, and starves the asyncio event loop. So the whole instance becomes slow, including token streaming in unrelated chats. The effect is cumulative: each occurrence costs another core. Nothing is logged.
Root cause
backend/open_webui/retrieval/web/utils.py:850 (sync loader):
page.route_web_socket('**/*', lambda ws_route: ws_route.close())
WebSocketRoute.close() is the synchronous API, called from inside a Playwright route callback. playwright/_impl/_sync_base.py:_sync() then runs:
task = self._loop.create_task(coro)
task.add_done_callback(lambda _: g_self.switch())
while not task.done():
self._dispatcher_fiber.switch() # no sleep, no yield to the OS
It waits for the dispatcher to complete the close while already executing inside that dispatcher, so the task can never complete and the loop spins forever.
Expected Behavior
Fetching a page that opens a WebSocket during web search should complete like any other page: the WebSocket is refused, the page content is extracted, the worker thread returns to the pool, and CPU returns to idle.
Actual Behavior
Three occurrences accumulated over a few days on a small instance: 100% CPU sustained with zero chat traffic, three leaked Playwright driver processes. A trivial endpoint (/api/version) went from 6 ms up to 256 ms, token streaming dropped to under 10 tok/s while the model server was idle and producing 138 tok/s per request. Restarting the container is the only recovery.
Steps to Reproduce
- WEB_LOADER_ENGINE=playwright, web search enabled
- Run a web search whose results include a page that opens a WebSocket (common on news, live-updating and chat-widget sites)
- Observe the container CPU rise and stay there permanently
Logs, Screenshots, and Config
WEB_LOADER_ENGINE=playwright, web search enabled
Additional Information
Connecting to a remote browser (PLAYWRIGHT_WS_URL / p.chromium.connect()) does not avoid the problem: sync_playwright() still starts a local driver process, and the spin is in the client-side sync wrapper. We hit this with an external browser container in use.
Before Submitting
devbranch or latest source.Installation Method
Docker
Open WebUI Version
v0.11.3
Operating System
Debian 13
Summary
Web search with
WEB_LOADER_ENGINE=playwrightpermanently spins a CPU core when a fetched page opens a WebSocket (syncws_route.close()re-enters the dispatcher)Every web-search page load that opens a WebSocket permanently loses one worker thread to a busy loop at 100% CPU. The thread never exits, holds the GIL, and starves the asyncio event loop. So the whole instance becomes slow, including token streaming in unrelated chats. The effect is cumulative: each occurrence costs another core. Nothing is logged.
Root cause
backend/open_webui/retrieval/web/utils.py:850(sync loader):WebSocketRoute.close()is the synchronous API, called from inside a Playwright route callback.playwright/_impl/_sync_base.py:_sync()then runs:It waits for the dispatcher to complete the close while already executing inside that dispatcher, so the task can never complete and the loop spins forever.
Expected Behavior
Fetching a page that opens a WebSocket during web search should complete like any other page: the WebSocket is refused, the page content is extracted, the worker thread returns to the pool, and CPU returns to idle.
Actual Behavior
Three occurrences accumulated over a few days on a small instance: 100% CPU sustained with zero chat traffic, three leaked Playwright driver processes. A trivial endpoint (
/api/version) went from 6 ms up to 256 ms, token streaming dropped to under 10 tok/s while the model server was idle and producing 138 tok/s per request. Restarting the container is the only recovery.Steps to Reproduce
Logs, Screenshots, and Config
WEB_LOADER_ENGINE=playwright, web search enabled
Additional Information
Connecting to a remote browser (
PLAYWRIGHT_WS_URL/p.chromium.connect()) does not avoid the problem:sync_playwright()still starts a local driver process, and the spin is in the client-side sync wrapper. We hit this with an external browser container in use.