Skip to content

ws.Client: module-level loop variable prevents multiple instances (multi-bot race condition) #119

@zj1123581321

Description

@zj1123581321

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

  1. Thread A: ws_mod.loop = loop_A
  2. Thread B: ws_mod.loop = loop_B (overwrites A)
  3. Thread A: cli.start() → reads ws_mod.loop → gets loop_Bloop_B.run_until_complete()
  4. Thread B: cli.start() → reads ws_mod.loop → gets loop_BERROR: 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions