Skip to content
This repository was archived by the owner on Jul 5, 2026. It is now read-only.

fix(actions): set ActionManager finished-event at init to satisfy count=0 invariant - #287

Open
Maurotb wants to merge 1 commit into
pipecat-ai:mainfrom
Maurotb:fix/action-manager-event-initial-state-invariant
Open

fix(actions): set ActionManager finished-event at init to satisfy count=0 invariant#287
Maurotb wants to merge 1 commit into
pipecat-ai:mainfrom
Maurotb:fix/action-manager-event-initial-state-invariant

Conversation

@Maurotb

@Maurotb Maurotb commented Jun 19, 2026

Copy link
Copy Markdown

ActionManager.__init__ set _ongoing_actions_count = 0 then created _ongoing_actions_finished_event = asyncio.Event(), which defaults to clear. That violates the natural invariant count = 0 ↔ event set: with no action ever in flight, an immediate await event.wait() blocks forever.

The counter helpers (_increment_ongoing_actions_count / _decrement_ongoing_actions_count) already flip the event correctly on transition, so the only buggy state was the initial one. Calling .set() at init restores the invariant; behaviour from the first action onward is unchanged.

Repro (pre-fix hangs forever, post-fix returns immediately):

am = ActionManager(worker, flow_manager)
await asyncio.wait_for(am._ongoing_actions_finished_event.wait(), timeout=0.1)

The added test (test_ongoing_actions_finished_event_is_set_at_init) pins both count == 0 and event.is_set() at init, plus a wait_for(..., timeout=0.1) that exercises the wait path. Fails on main, passes after the fix.

Downstream context: we've been working around this with a small monkey-patch in our worker (flows.ActionManager.__init__ wrapped to call .set() after init) since shortly after picking up Flows. Switching to the fix on rebuild is a one-liner and would let us drop the patch.

``ActionManager.__init__`` set ``_ongoing_actions_count = 0`` and built
``_ongoing_actions_finished_event = asyncio.Event()``, which defaults to
the clear state. This violates the natural invariant *count=0 ↔ event
set*: an immediate ``await event.wait()`` blocks forever even though no
action is in flight.

The counter helpers (``_increment_ongoing_actions_count`` /
``_decrement_ongoing_actions_count``) flip the event correctly on
transition, so the only buggy state was the initial one. Set the event
at init time to restore the invariant; behaviour from the first action
onward is unchanged.

Reproducer: ``await ActionManager(worker, mgr)._ongoing_actions_finished_event.wait()``
hung forever pre-fix; returns immediately post-fix. Added a regression
test pinning both ``count == 0`` and ``event.is_set()`` at init plus a
``wait_for(..., timeout=0.1)`` that exercises the path.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant