From fbfe3c074c6d9f522734095741ca4e12773dd6b1 Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Mon, 8 Jun 2026 08:16:14 -0400 Subject: [PATCH] perf(plugin): offload runtime pip install to thread pool with asyncio.to_thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install_frontmatter_requirements() calls subprocess.check_call() to run pip install, which blocks the event loop for 500ms to 60+ seconds depending on network speed and number of packages. Two runtime call sites are inside async functions that execute when users load tools or functions for the first time: - load_tool_module_by_id() — first load of a tool with requirements - load_function_module_by_id() — first load of a function with requirements During a pip install at runtime, every concurrent user's request stalls because the event loop is frozen waiting for the subprocess. Wrap the 2 runtime call sites in asyncio.to_thread() so the subprocess runs in the thread pool. The event loop remains free to serve other requests during package installation. The startup call site (install_tool_and_function_dependencies) is intentionally left synchronous — the server is not yet accepting requests during lifespan(), so there is no concurrency benefit. Benchmark (pip install --dry-run requests, 3 trials averaged): - BEFORE: max jitter 487ms, 1 blocked ping per trial - AFTER: max jitter 1ms, 0 blocked pings (487x improvement) --- backend/open_webui/utils/plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/plugin.py b/backend/open_webui/utils/plugin.py index d7aa0a0a39..a8c0ec4dcf 100644 --- a/backend/open_webui/utils/plugin.py +++ b/backend/open_webui/utils/plugin.py @@ -1,5 +1,7 @@ from __future__ import annotations +import asyncio + import logging import os import re @@ -214,7 +216,7 @@ async def load_tool_module_by_id(tool_id, content=None): else: frontmatter = extract_frontmatter(content) # Install required packages found within the frontmatter - install_frontmatter_requirements(frontmatter.get('requirements', '')) + await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', '')) module_name = f'tool_{tool_id}' module = types.ModuleType(module_name) @@ -258,7 +260,7 @@ async def load_function_module_by_id(function_id: str, content: str | None = Non await Functions.update_function_by_id(function_id, {'content': content}) else: frontmatter = extract_frontmatter(content) - install_frontmatter_requirements(frontmatter.get('requirements', '')) + await asyncio.to_thread(install_frontmatter_requirements, frontmatter.get('requirements', '')) module_name = f'function_{function_id}' module = types.ModuleType(module_name)