Context Propagation & Tracking
Relevant source files
The following files were used as context for generating this wiki page:
Context Propagation and Tracking in the bbus ecosystem ensures that both environmental metadata (e.g., Request IDs, User IDs) and structural lineage (parent-child event relationships) are preserved across asynchronous execution boundaries. The system provides a unified way to track an event's origin, the path it has traveled across different buses, and the execution context in which it was dispatched.
This system is critical for debugging distributed event flows, enforcing timeouts across nested operations, and maintaining audit logs through middleware.
Lineage & Structural Tracking
Structural tracking is handled via metadata fields embedded directly in every BaseEvent. These fields allow the system to reconstruct the "family tree" of events, even when they are dispatched across multiple buses or bridge boundaries.
Core Lineage Fields
Every event inherits several tracking fields defined in the BaseEvent schema:
| Field | Type | Description |
|---|---|---|
event_id | UUID | A unique identifier (UUIDv7) generated at creation. |
event_parent_id | UUID | null | The ID of the event that was being handled when this event was emitted. |
event_path | string[] | A list of bus labels (e.g., BusName#id) the event has passed through. |
event_emitted_by_handler_id | UUID | null | The specific handler instance ID that emitted this event. |
Sources: bubus-ts/src/base_event.ts:24-42, bubus-ts/src/base_event.ts:114-123
Automatic Linkage
Lineage is automatically captured when using the event.bus proxy within a handler. When a handler dispatches a new event using the bus instance provided in its arguments, the library automatically sets the event_parent_id and event_emitted_by_handler_id on the new event.
The diagram shows how metadata is inherited when an event is emitted from within the context of an active handler.
Sources: bubus-ts/examples/parent_child_tracking.ts:31-40, bubus-ts/src/base_event.ts:384-398
Execution Context Propagation
Execution context propagation refers to the persistence of non-event data (like logging spans or security tokens) using language-level primitives: ContextVars in Python and AsyncLocalStorage in TypeScript.
Implementation Mechanics
The system captures the context at the moment of dispatch and restores it during execution. This ensures that if a developer sets a context variable before calling bus.dispatch(), that variable is available inside the handler, even if the handler runs much later or on a different event loop tick.
- Capture Phase: When
EventBus.dispatch()(TS) orEventBus.emit()(Py) is called, the current async context is snapshotted. - Storage Phase: The snapshotted context is stored as an internal property on the event object (
_event_dispatch_context). - Restore Phase: Before the handler function is invoked, the library enters the snapshotted context, making the variables accessible.
Sources: TODO_ts.md:46-48, TODO_python.md:43-45, tests/test_context_propagation.py:46-52
Context Lifecycle Diagram
This sequence illustrates the "Capture at Dispatch, Restore at Execution" pattern used to maintain context across the asynchronous queue.
Sources: bubus-ts/tests/context_propagation.test.ts:27-37, tests/test_context_propagation.py:102-115
Cross-Bus Propagation & Forwarding
Context and lineage are preserved even when events are forwarded between different EventBus instances. This is vital for complex architectures using bridges (e.g., Redis, Postgres, or Socket bridges).
Event Pathing
The event_path field acts as a breadcrumb trail. Each bus that processes or forwards an event appends its unique label (Name + ID) to this array. This allows for cycle detection and provides a full audit trail of the event's journey through the system.
Parent Tracking Consistency
When a parent event is dispatched on Bus A, and it emits a child event that is forwarded to Bus B, the event_parent_id remains constant. EventBus.findEventById() and EventBus.eventIsChildOf() can resolve these relationships across buses by checking local histories and paths.
Sources: bubus-ts/examples/parent_child_tracking.ts:91-106, bubus-ts/src/base_event.ts:413-421
Timeout & Cancellation Context
Lineage tracking is not only for observability but also drives functional logic like Cascading Cancellations. When an event reaches its event_timeout, the system uses the tracking metadata to identify all active descendant events and cancel them.
Cancellation Flow
If a parent handler times out:
- The parent handler is aborted with an
EventHandlerTimeoutError. - The system calls
cancelPendingDescendants(). - It walks the
event_childrentree (populated viaevent_results). - Pending child handlers are cancelled with
EventHandlerCancelledError. - Started child handlers are aborted with
EventHandlerAbortedError.
Sources: bubus-ts/src/base_event.ts:460-496, bubus-ts/tests/timeout.test.ts:503-520
Error Taxonomy in Context
| Error Class | Trigger Context |
|---|---|
EventHandlerTimeoutError | The handler exceeded its own handler_timeout or the event's event_timeout. |
EventHandlerCancelledError | The handler was in the queue (pending) but its parent event or handler was cancelled/timed out. |
EventHandlerAbortedError | The handler was already running (started) but was force-stopped because its parent timed out. |
Sources: TODO_ts.md:20-25, bubus-ts/tests/timeout.test.ts:80-120
Conclusion
Context Propagation & Tracking provides the backbone for both observability and operational safety in bbus. By combining structural lineage (event_id, event_parent_id) with execution context capture (ALS/ContextVars), the library ensures that developers can trace the "why" and "where" of every event, while also enabling sophisticated features like cross-bus lineage resolution and automatic cascading timeouts.
The integration of these systems allows bbus to maintain a consistent state across distributed or heavily nested asynchronous workflows, providing the necessary tools for robust error handling and auditing.