What Python's asyncio primitives get wrong about shared state
7 hours ago
- #asyncio
- #concurrency
- #python
- Python's asyncio primitives like Event, Condition, and Queue have limitations in handling shared state under real concurrency.
- Polling for state changes introduces tradeoffs between latency and efficiency, leading to wasted CPU cycles and arbitrary sleep intervals.
- asyncio.Event simplifies waiting for state changes but is limited to boolean states, requiring multiple events for complex state machines.
- asyncio.Condition allows waiting on arbitrary predicates but can miss intermediate states due to the 'lost update' problem in fast transitions.
- A solution using per-consumer queues ensures no state transitions are missed by buffering every change for each consumer.
- The ValueWatcher class provides a production-ready implementation with thread safety, atomic registration, generic typing, and various wait methods.
- ValueWatcher handles common patterns like waiting for specific states, non-None values, or any change, with support for timeouts and callbacks.
- The implementation avoids the pitfalls of asyncio.Condition by ensuring consumers see every state transition, not just the current state.
- ValueWatcher is used in Inngest's Python SDK for managing WebSocket connections, worker lifecycle, and graceful shutdown.