From c61a8fa400d4ed9340e6f8b9a7521a399138fcc3 Mon Sep 17 00:00:00 2001 From: Anubhav Dhawan Date: Mon, 15 Dec 2025 16:52:18 +0530 Subject: [PATCH] fix(toolbox-core): fix race condition in SyncClient loop init Fixes a race condition in ToolboxSyncClient where multiple threads could initialize separate event loops, causing aiohttp to raise RuntimeError. This change adds a thread lock to ensure the shared background event loop is initialized exactly once. --- .../toolbox-core/src/toolbox_core/sync_client.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/toolbox-core/src/toolbox_core/sync_client.py b/packages/toolbox-core/src/toolbox_core/sync_client.py index 4826fc05c..c7edaaf5e 100644 --- a/packages/toolbox-core/src/toolbox_core/sync_client.py +++ b/packages/toolbox-core/src/toolbox_core/sync_client.py @@ -14,7 +14,7 @@ from asyncio import AbstractEventLoop, new_event_loop, run_coroutine_threadsafe -from threading import Thread +from threading import Lock, Thread from typing import Any, Awaitable, Callable, Mapping, Optional, Union from deprecated import deprecated @@ -34,6 +34,7 @@ class ToolboxSyncClient: __loop: Optional[AbstractEventLoop] = None __thread: Optional[Thread] = None + __lock: Lock = Lock() def __init__( self, @@ -53,11 +54,13 @@ def __init__( # Running a loop in a background thread allows us to support async # methods from non-async environments. if self.__class__.__loop is None: - loop = new_event_loop() - thread = Thread(target=loop.run_forever, daemon=True) - thread.start() - self.__class__.__thread = thread - self.__class__.__loop = loop + with self.__class__.__lock: + if self.__class__.__loop is None: + loop = new_event_loop() + thread = Thread(target=loop.run_forever, daemon=True) + thread.start() + self.__class__.__thread = thread + self.__class__.__loop = loop async def create_client(): return ToolboxClient(url, client_headers=client_headers, protocol=protocol)