Feature Request: Add non-gather mode for AsyncMachine callbacks
Problem Description
Currently, AsyncMachine executes callbacks using asyncio.gather(), which creates a potential issue when managing resource lifecycles across different state transitions.
The problem manifests when:
- Opening resources in one transition callback (e.g.,
on_enter_connect)
- Closing those same resources in another transition callback (e.g.,
on_enter_disconnect)
Because asyncio.gather() runs callbacks in separate tasks, this creates a task boundary violation for context managers that require opening/closing in the same task (e.g., anyio cancel scopes, async_exit_stack, etc.).
Current Workaround
I've implemented a temporary solution by subclassing AsyncMachine:
class A2CAsyncMachine(AsyncMachine):
@staticmethod
async def await_all(callables: list[Callable]) -> list:
ret = []
for c in callables:
ret.append(await c())
return ret
This works but isn't ideal for long-term maintenance.
Proposed Solution
Add a configuration flag to control callback execution mode:
machine = AsyncMachine(..., gather_callbacks=False) # Default True for backward compatibility
When False, callbacks would execute sequentially in the same task rather than via asyncio.gather().
Benefits
- Maintains backward compatibility
- Gives users control over execution model
- Solves task-boundary issues for context managers
- Still allows parallel execution when desired (default mode)
Potential Considerations
- Performance impact for users who don't need sequential execution
- Need to verify no other parts of the code assume gather behavior
- Documentation updates to explain the tradeoffs
Environment
- OS: macOS Ventura
- Python: 3.11
- Library: transitions (latest main branch)
Would appreciate maintainers' thoughts on this approach. Happy to submit a PR if this direction is approved.
Feature Request: Add non-gather mode for AsyncMachine callbacks
Problem Description
Currently,
AsyncMachineexecutes callbacks usingasyncio.gather(), which creates a potential issue when managing resource lifecycles across different state transitions.The problem manifests when:
on_enter_connect)on_enter_disconnect)Because
asyncio.gather()runs callbacks in separate tasks, this creates a task boundary violation for context managers that require opening/closing in the same task (e.g.,anyiocancel scopes,async_exit_stack, etc.).Current Workaround
I've implemented a temporary solution by subclassing
AsyncMachine:This works but isn't ideal for long-term maintenance.
Proposed Solution
Add a configuration flag to control callback execution mode:
When
False, callbacks would execute sequentially in the same task rather than viaasyncio.gather().Benefits
Potential Considerations
Environment
Would appreciate maintainers' thoughts on this approach. Happy to submit a PR if this direction is approved.