Event Queue (EventQueue)¶
Overview¶
EventQueue is a replaceable abstraction for the bus's internal dispatch queue. It
decouples the "queue" from EventBus: the bus only depends on the EventQueue
interface for enqueue-on-publish, dequeue-on-dispatch, and drain-on-shutdown — it never
knows about any concrete implementation or its configuration. Bounded/unbounded,
persistent, priority, or cross-process semantics are entirely up to the injected queue.
| Symbol | Description |
|---|---|
EventQueue |
Queue abstract base class (ABC); the minimal queue protocol the bus needs |
InMemoryEventQueue |
Default in-process implementation backed by asyncio.Queue, supports bounded backpressure |
InMemoryEventQueueConfig |
The in-memory queue's own config model (independent from the bus) |
EventQueue (ABC)¶
class EventQueue(ABC):
async def put(self, event: Event) -> None # enqueue; blocks when full (backpressure)
async def get(self) -> Event # pop head; blocks when empty
def task_done(self) -> None # mark one event as processed
def qsize(self) -> int # current number of pending events
async def join(self) -> None # wait until the queue is drained
| Method | Description |
|---|---|
put(event) |
Enqueues on the publishing side. Blocks until a slot frees when full — backpressure. |
get() |
Dequeues on the dispatch side. Blocks until an event arrives when empty. |
task_done() |
Called after each event fetched by get() is processed; used by join() to detect drain. |
qsize() |
Backlog depth; used for observability and drain-timeout estimation on shutdown. |
join() |
Blocks until every enqueued event has been fetched and task_done-ed (key to no-loss graceful shutdown). |
Implementers only need to satisfy this minimal protocol to be usable by the bus; capacity policy, storage medium, and queue semantics are the implementation's own responsibility.
InMemoryEventQueueConfig¶
The in-memory queue's own configuration — not owned by the bus.
class InMemoryEventQueueConfig(BaseModel):
maxsize: int = 1024 # max capacity; 0 means unbounded (asyncio.Queue semantics)
| Field | Type | Default | Description |
|---|---|---|---|
maxsize |
int |
1024 |
Max queue capacity; put blocks when full; 0 means unbounded. |
InMemoryEventQueue¶
Default implementation backed by asyncio.Queue; the fallback when the bus is built
without an injected queue.
queue = InMemoryEventQueue() # default bounded 1024
queue = InMemoryEventQueue(InMemoryEventQueueConfig(maxsize=0)) # unbounded
queue = InMemoryEventQueue(InMemoryEventQueueConfig(maxsize=100)) # bounded 100
Injection into EventBus¶
EventBus receives an EventQueue instance through the queue constructor parameter;
when omitted, it creates an InMemoryEventQueue() internally (capacity from the queue's
own default config, 1024).
class EventBus:
def __init__(
self,
event_registry: EventRegistry,
handler_registry: EventHandlerRegistry,
queue: Optional[EventQueue] = None, # injected queue; default InMemoryEventQueue()
router: Optional[Router] = None, # injected router; default Router(event_registry)
max_handler_semaphore: int = 256,
shutdown: ShutdownConfig = ShutdownConfig(),
middleware_chain: Optional[MiddlewareChain] = None,
) -> None
| Parameter | Type | Default | Description |
|---|---|---|---|
queue |
Optional[EventQueue] |
None |
The internal dispatch queue. Defaults to the in-process implementation with its own default capacity. |
Decoupling: queue capacity and other settings live on the queue itself (
InMemoryEventQueueConfig).EventBusneither knows nor forwards any queue config.
Use Cases¶
Inject a bounded queue (replaces old max_queue_size)¶
from infinity_bus import EventBus, InMemoryEventQueue, InMemoryEventQueueConfig
# Old: EventBus(reg, hreg, max_queue_size=100)
queue = InMemoryEventQueue(InMemoryEventQueueConfig(maxsize=100))
bus = EventBus(reg, hreg, queue=queue)
Inject a custom queue (persistent / priority / cross-process)¶
Just implement the five EventQueue methods to swap the internal queue:
from infinity_bus import EventQueue, Event
class PriorityEventQueue(EventQueue):
"""Example: priority-ordered custom queue (sketch; implement the full protocol)."""
async def put(self, event: Event) -> None: ...
async def get(self) -> Event: ...
def task_done(self) -> None: ...
def qsize(self) -> int: ...
async def join(self) -> None: ...
bus = EventBus(reg, hreg, queue=PriorityEventQueue())
Workflow¶
- Construction:
EventBusstores the injectedEventQueueas its dispatch queue. - Publish:
_core_publishcallsawait queue.put(event); publishers block when the queue is full (backpressure). - Dispatch: the loop does
event = await queue.get()→ match handlers →queue.task_done(). - Shutdown:
stop()estimates a dynamic timeout fromqueue.qsize()and awaitsqueue.join()to drain the queue without losing events.
Notes¶
- The
EventQueueprotocol is intentionally minimal — only the 5 methods the bus needs. Non-bus helpers (empty/full/put_nowait) stay out of the abstraction; implementations may add them freely. - Custom implementations must honor the
task_done/joinaccounting semantics (exactly onetask_doneperget), otherwise shutdown drain may block forever. - To get capacity control equivalent to the old
max_queue_size, injectInMemoryEventQueue(InMemoryEventQueueConfig(maxsize=N)).