-
Notifications
You must be signed in to change notification settings - Fork 84
Description
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()→ readsws_mod.loop→ getsloop_B→loop_B.run_until_complete() - Thread B:
cli.start()→ readsws_mod.loop→ getsloop_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
- 建议添加 async的start方法里面的loop在异步的框架下会报错 #96 —
loopconflicts with async frameworks - 个人拙见:真有人在生产用这个库吗?感觉高并发下会崩 #109 — concerns about production readiness
Environment
- lark_oapi version: latest (PyPI)
- Python: 3.13
- Use case: multi-bot IM middleware (each bot = separate
ws.Clientinstance in its own thread)