Skip to content

fix(py/triggers): stop() deadlock + subscribe() timeout connection leak#3890

Open
Checo (sergioperezcheco) wants to merge 1 commit into
ComposioHQ:nextfrom
sergioperezcheco:sergioperezcheco/py/fix-trigger-stop-deadlock
Open

fix(py/triggers): stop() deadlock + subscribe() timeout connection leak#3890
Checo (sergioperezcheco) wants to merge 1 commit into
ComposioHQ:nextfrom
sergioperezcheco:sergioperezcheco/py/fix-trigger-stop-deadlock

Conversation

@sergioperezcheco

Copy link
Copy Markdown

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.

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).
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

Checo (@sergioperezcheco) is attempting to deploy a commit to the Composio Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Trigger subscription stop() from a callback deadlocks the process; subscribe() timeout leaksa forever-reconnecting websocket thread

1 participant