How to Configure Event Timeouts in Bubus: A Complete Guide

Bubus provides a hierarchical timeout system that lets you configure default limits on the BaseEvent model, override them per-event instance, or set specific limits per-handler when dispatching or awaiting results.

The browser-use/bubus event bus library implements a robust timeout mechanism to prevent runaway handlers from blocking your application. Whether you're building long-running background processors or latency-critical user interfaces, knowing how to configure event timeouts in Bubus ensures your system remains responsive and predictable.

Understanding the Timeout Architecture

Bubus implements timeouts at multiple layers, from the core data model to the execution service. The system respects the most specific timeout provided, falling back to broader defaults when specific values are omitted.

Default Event Timeout in BaseEvent

Every event in Bubus inherits from BaseEvent defined in bubus/models.py. The model declares an event_timeout field with a default value of 300 seconds (5 minutes):


# bubus/models.py L22-L24

class BaseEvent(BaseModel):
    event_timeout: float | None = 300.0

This value serves as the global default for any event instance that does not specify its own timeout.

Per-Handler Timeout Overrides

When the event bus executes a handler, it creates an EventResult object that tracks the execution. According to the source code in bubus/service.py, the effective timeout for a specific handler is determined by:


# bubus/service.py L972-L973 and L1099-L1100

timeout = timeout or event.event_timeout

This means an explicit timeout argument passed to the dispatch or processing method takes precedence over the event's default timeout.

Configuring Timeouts at Different Layers

You can configure timeouts globally, per-instance, per-handler, or when awaiting results. Each approach targets different use cases within the Bubus framework.

Set a Global Default Timeout

To change the default for all events of a specific type, subclass BaseEvent and override the event_timeout field:

from bubus.models import BaseEvent

class MyEvent(BaseEvent):
    event_timeout: float | None = 60.0  # 1 minute default for all MyEvent instances

This approach ensures consistency across your application for specific event types without manually passing timeouts during instantiation.

Configure Per-Instance Timeouts

For one-off events requiring different limits, pass the event_timeout parameter during construction:

from bubus.models import BaseEvent

# Create an event with a 5-second timeout

event = BaseEvent(
    event_type='QuickTask',
    event_timeout=5.0
)
bus.dispatch(event)

This overrides the class default for that specific instance only.

Override Timeouts Per-Handler

When dispatching events through low-level APIs like step or process_event, you can specify a handler timeout that overrides both the event default and instance values:


# Force a 2-second limit regardless of event.event_timeout

await bus.step(event, timeout=2.0)

This is useful when the same event type might be processed by different handlers with varying latency requirements.

Timeouts When Waiting for Events

The expect and wait_until_idle methods accept timeout parameters to prevent indefinite blocking:


# bubus/service.py L591-L617 (expect) and L817-L839 (wait_until_idle)

# Wait up to 3 seconds for a specific event type

user_evt = await bus.expect('UserActionEvent', timeout=3.0)

# Wait until bus is idle, but timeout after 10 seconds

await bus.wait_until_idle(timeout=10.0)

If the timeout expires, these methods raise asyncio.TimeoutError.

Timeouts When Awaiting Results

When retrieving results from specific handlers, pass the timeout to event_result:


# bubus/models.py L485-L486

result = await my_event.event_result(
    handler_name='process_data',
    timeout=4.5
)

This forwards the timeout directly to asyncio.wait_for internally.

What Happens When a Timeout Occurs

Understanding the timeout behavior helps you implement proper error handling and cleanup logic.

When a handler exceeds its allotted time, Bubus performs the following actions as implemented in bubus/service.py (lines 1184-1194):

  1. Task Cancellation: The handler's asyncio task is cancelled immediately.
  2. Error Recording: A TimeoutError is recorded on the associated EventResult object.
  3. Child Event Cleanup: Any child events spawned by the timed-out handler are cancelled via event.event_cancel_pending_child_processing.
  4. Structured Logging: The system logs a timeout tree via log_timeout_tree to help diagnose which handler exceeded its budget.

This comprehensive cleanup ensures that timeout cascades don't leave orphaned tasks or inconsistent state in your application.

Summary

  • Bubus provides a hierarchical timeout system with defaults at the BaseEvent level (300 seconds), overridable per-instance, per-handler, and during wait operations.
  • Configure global defaults by subclassing BaseEvent and setting event_timeout.
  • Override per-instance by passing event_timeout during event construction.
  • Override per-handler by passing timeout to bus.step() or similar low-level APIs.
  • Set wait timeouts using the timeout parameter in bus.expect() and bus.wait_until_idle().
  • Await results with timeouts by passing timeout to event.event_result().
  • Timeout behavior includes task cancellation, error recording, child event cleanup, and structured logging via log_timeout_tree in bubus/service.py.

Frequently Asked Questions

What is the default timeout for events in Bubus?

The default timeout is 300 seconds (5 minutes), defined in the event_timeout field of the BaseEvent model in bubus/models.py (lines 22-24). You can override this default globally by subclassing BaseEvent or per-instance when creating specific events.

How do I set a different timeout for a specific event handler?

You can override the timeout for a specific handler dispatch by passing the timeout argument to low-level methods like bus.step(). According to the implementation in bubus/service.py (lines 972-973 and 1099-1100), the explicit timeout parameter takes precedence over the event's event_timeout attribute.

What exception is raised when an event times out in Bubus?

When waiting for events using bus.expect() or bus.wait_until_idle(), Bubus raises asyncio.TimeoutError if the specified timeout expires. For handler execution timeouts, the system records a TimeoutError on the EventResult object and cancels the handler task, as implemented in bubus/service.py (lines 1184-1194).

Does Bubus clean up child events when a parent handler times out?

Yes. When a handler exceeds its timeout budget, Bubus automatically cancels any child events that were spawned during the handler's execution. This cleanup is performed via event.event_cancel_pending_child_processing as part of the timeout handling logic in bubus/service.py, ensuring that timeout cascades don't leave orphaned tasks running.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →