Skip to content

fix: sync Playwright loader spins a CPU core when a fetched page opens a WebSocket #30024

Description

@nmzz

Before Submitting

  • I searched open and closed issues and discussions for an existing report.
  • I checked whether this is already fixed on the dev branch or latest source.
  • I understand that maintainers want a well-written issue before any code pull request.
  • I am using the latest available version of Open WebUI for my install method.
  • This is not a security vulnerability.

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

  1. WEB_LOADER_ENGINE=playwright, web search enabled
  2. Run a web search whose results include a page that opens a WebSocket (common on news, live-updating and chat-widget sites)
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions