fix(py/triggers): stop() deadlock + subscribe() timeout connection leak#3890
Open
Checo (sergioperezcheco) wants to merge 1 commit into
Open
Conversation
Two lifecycle bugs in TriggerSubscription (python/composio/core/models/triggers.py), both reported in ComposioHQ#3858. Bug 1 — stop() called from a trigger callback deadlocks the whole process. pysher's Connection.disconnect does socket.close(); join(timeout) on the websocket thread, but callbacks run on that thread's dispatch path (_handle_event blocks on the callback's future.result()). Joining the thread from a callback that runs on it deadlocks. Fix: clear _alive synchronously (so wait_forever unblocks) and run disconnect on a daemon thread. Bug 2 — _SubcriptionBuilder.connect() timeout leaks a websocket thread. The deadline path raised ComposioSDKTimeoutError without ever calling pusher.disconnect(), so pysher's run loop kept redialing every reconnect_interval for the life of the process (one leaked thread per timed-out subscribe(); a later success would silently subscribe a forgotten channel). Fix: wrap the wait loop in try/except so the timeout path disconnects before re-raising. Regression tests: TestTriggerSubscriptionStop (Bug 1) and TestSubscriptionBuilderConnectTimeout (Bug 2).
Checo (sergioperezcheco)
requested a review
from Alberto Schiabel (jkomyno)
as a code owner
July 18, 2026 23:22
|
Checo (@sergioperezcheco) is attempting to deploy a commit to the Composio Team on Vercel. A member of the Team first needs to authorize it. |
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.
Fixes #3858 (both bugs reported there).
Bug 1 — TriggerSubscription.stop() deadlocks when called from a trigger callback. pysher's Connection.disconnect runs socket.close() then join(timeout) on the websocket thread, but callbacks execute on that same thread's dispatch path (_handle_event blocks on the callback's future.result()), so joining the thread from within a callback that runs on it deadlocks the whole process and _alive is never cleared, so a main thread parked in wait_forever() never exits. Fix: clear _alive synchronously (so wait_forever unblocks immediately) and run disconnect() on a daemon thread, breaking the reentrancy while still closing the socket.
Bug 2 — _SubcriptionBuilder.connect() leaks a websocket thread on timeout. The deadline path raised ComposioSDKTimeoutError without calling pusher.disconnect(), so pysher's run loop kept redialing every reconnect_interval for the life of the process — one leaked thread per timed-out subscribe(), and if a leaked connection later succeeded it would authenticate and subscribe the channel of an abandoned TriggerSubscription, silently consuming events. Fix: wrap the wait loop in try/except so the timeout path tears the connection down before re-raising.
Added regression tests (TestTriggerSubscriptionStop and TestSubscriptionBuilderConnectTimeout) that reproduce both failure modes offline with mocked pusher connections; all 73 tests in tests/test_triggers.py pass, ruff check + format clean.