larksuite / larksuite/oapi-sdk-python

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

Open
#119 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
559
Forks
102
PR merge metrics
No merged PRs in 30d

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
  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

  • #96 — loop conflicts 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.Client instance in its own thread)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lark_oapi/ws/client.py around the module-level loop at lines 26-29, then trace Client.start() and _receive_message_loop at line 171. Reproduce the separate-thread, multiple-bot case described in the issue and determine the intended instance or async API approach. Done means independent ws.Client instances no longer share or overwrite an event loop and can run concurrently without the reported RuntimeError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.