Problem
lark_oapi/ws/client.py uses a module-level loop variable (line 26-29):
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
All ws.Client instances share this single loop reference. When multiple Feishu bots are started in separate threads (a common pattern for multi-bot applications), they overwrite each other's loop, causing RuntimeError: This event loop is already running.
Race condition
- Thread A:
ws_mod.loop = loop_A
- Thread B:
ws_mod.loop = loop_B (overwrites A)
- Thread A:
cli.start() → reads ws_mod.loop → gets loop_B → loop_B.run_until_complete()
- Thread B:
cli.start() → reads ws_mod.loop → gets loop_B → ERROR: already running
Even after connecting, _receive_message_loop (line 171) uses loop.create_task() which may reference the wrong loop.
Current workaround
We replaced ws_mod.loop with a thread-local proxy:
class _ThreadLocalLoopProxy:
def __getattr__(self, name):
return getattr(asyncio.get_event_loop(), name)
ws_mod.loop = _ThreadLocalLoopProxy()
This works but is fragile against SDK changes.
Suggested fix
Make ws.Client instance-based, like the Node.js SDK (@larksuiteoapi/node-sdk) already does with WSClient:
class Client:
def __init__(self, ...):
self._loop = asyncio.new_event_loop()
...
def start(self):
self._loop.run_until_complete(self._connect())
self._loop.create_task(self._ping_loop())
self._loop.run_until_complete(_select())
Or provide an async start_async() method (as suggested in #96) so the client can integrate with existing event loops (e.g., uvicorn, FastAPI).
Related issues
Environment
- lark_oapi version: latest (PyPI)
- Python: 3.13
- Use case: multi-bot IM middleware (each bot = separate
ws.Client instance in its own thread)
Problem
lark_oapi/ws/client.pyuses a module-levelloopvariable (line 26-29):All
ws.Clientinstances share this singleloopreference. When multiple Feishu bots are started in separate threads (a common pattern for multi-bot applications), they overwrite each other's loop, causingRuntimeError: This event loop is already running.Race condition
ws_mod.loop = loop_Aws_mod.loop = loop_B(overwrites A)cli.start()→ readsws_mod.loop→ getsloop_B→loop_B.run_until_complete()cli.start()→ readsws_mod.loop→ getsloop_B→ ERROR: already runningEven after connecting,
_receive_message_loop(line 171) usesloop.create_task()which may reference the wrong loop.Current workaround
We replaced
ws_mod.loopwith a thread-local proxy:This works but is fragile against SDK changes.
Suggested fix
Make
ws.Clientinstance-based, like the Node.js SDK (@larksuiteoapi/node-sdk) already does withWSClient:Or provide an async
start_async()method (as suggested in #96) so the client can integrate with existing event loops (e.g., uvicorn, FastAPI).Related issues
loopconflicts with async frameworksEnvironment
ws.Clientinstance in its own thread)