fix(ws): isolate transport event loops per client - #10
Open
wufei-png wants to merge 2 commits into
Open
Conversation
wufei-png
marked this pull request as ready for review
August 14, 2026 10:18
wufei-png
force-pushed
the
codex/fix-ws-client-loop-ownership
branch
from
August 14, 2026 10:21
704ed7d to
e32f304
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ws.Client.start()invocationRoot cause
lark_channel.ws.clientcapturedasyncio.get_event_loop()in a module global. If the module was first imported from an already-running asyncio application,FeishuChannel.start_background()later ranClient.start()in an executor thread, butClient.start()still calledrun_until_complete()on the caller's running loop. This raisedRuntimeError: This event loop is already running.The same module global was shared by every client, so independently started clients could also race on one loop.
ExpiringCacheseparately captured the ambient loop during construction, and the high-level Channel shutdown path looked the loop up from the module instead of the client instance.Related reports against the legacy Python SDK describe the same shared-loop failure modes:
Commit structure
fix(channel): stop websocket clients on their own loopfix(ws): isolate event loops per clientThe first commit is independently valid with the existing client because it already records its selected loop on the instance. The second commit removes the shared loop and completes per-client ownership.
Validation
62 passed62 passed1101 passed, 1 deselectedupstream/mainwhen the pytest temp path contains a backslashpython -m build: sdist and wheel built successfullygit diff --check: passedNon-goals
This PR does not change WebSocket data-frame message-type routing or the existing
MessageType.CARDbehavior.