BUG-HUNT: [resource-leak] EventBusBridge.start() leaks first subscription handle when called more than once #7729

Open
opened 2026-04-12 03:21:25 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: [Resource Leak] — EventBusBridge Double-Start Leaks First Subscription

Severity Assessment

  • Impact: Calling start() twice leaves the first event-bus subscription orphaned (never disposed), causing every domain event to be forwarded to the A2A queue twice and permanently leaking a subscription handle in the underlying event bus
  • Likelihood: Medium — any code that restarts the bridge (e.g. reconnect logic, test setup) without calling stop() first will trigger this
  • Priority: High

Location

  • File: src/cleveragents/a2a/events.py
  • Function/Class: EventBusBridge.start()
  • Lines: ~198–203

Description

EventBusBridge.start() overwrites self._subscription with a new handle every time it is called, without first disposing the existing handle. The first subscription continues receiving domain events and forwarding them to the queue, while only the second handle is tracked for future stop() calls.

Evidence

class EventBusBridge:
    def __init__(self, event_bus, event_queue) -> None:
        self._subscription: Any | None = None

    def start(self) -> None:
        """Subscribe to the event bus and begin forwarding."""
        if hasattr(self._event_bus, "subscribe"):
            # BUG: if self._subscription is already set, the old handle
            # is overwritten without being disposed first
            self._subscription = self._event_bus.subscribe(self._on_domain_event)
            logger.info("a2a.event_bridge.started")

    def stop(self) -> None:
        if self._subscription is not None:
            if hasattr(self._subscription, "dispose"):
                self._subscription.dispose()  # only disposes the SECOND subscription
            self._subscription = None

Expected Behavior

Calling start() on an already-started bridge should either be a no-op (idempotent) or automatically dispose the existing subscription before subscribing again. No subscription should be leaked.

Actual Behavior

The first subscription handle is lost (overwritten with no reference). Domain events are delivered twice to the queue. After stop(), only the second subscription is disposed; the first continues delivering events indefinitely until the event bus is torn down.

Suggested Fix

def start(self) -> None:
    """Subscribe to the event bus and begin forwarding."""
    if self._subscription is not None:
        # Already started — idempotent guard
        logger.warning("a2a.event_bridge.already_started")
        return
    if hasattr(self._event_bus, "subscribe"):
        self._subscription = self._event_bus.subscribe(self._on_domain_event)
        logger.info("a2a.event_bridge.started")

Category

resource-management

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [Resource Leak] — EventBusBridge Double-Start Leaks First Subscription ### Severity Assessment - **Impact**: Calling `start()` twice leaves the first event-bus subscription orphaned (never disposed), causing every domain event to be forwarded to the A2A queue **twice** and permanently leaking a subscription handle in the underlying event bus - **Likelihood**: Medium — any code that restarts the bridge (e.g. reconnect logic, test setup) without calling `stop()` first will trigger this - **Priority**: High ### Location - **File**: `src/cleveragents/a2a/events.py` - **Function/Class**: `EventBusBridge.start()` - **Lines**: ~198–203 ### Description `EventBusBridge.start()` overwrites `self._subscription` with a new handle every time it is called, without first disposing the existing handle. The first subscription continues receiving domain events and forwarding them to the queue, while only the second handle is tracked for future `stop()` calls. ### Evidence ```python class EventBusBridge: def __init__(self, event_bus, event_queue) -> None: self._subscription: Any | None = None def start(self) -> None: """Subscribe to the event bus and begin forwarding.""" if hasattr(self._event_bus, "subscribe"): # BUG: if self._subscription is already set, the old handle # is overwritten without being disposed first self._subscription = self._event_bus.subscribe(self._on_domain_event) logger.info("a2a.event_bridge.started") def stop(self) -> None: if self._subscription is not None: if hasattr(self._subscription, "dispose"): self._subscription.dispose() # only disposes the SECOND subscription self._subscription = None ``` ### Expected Behavior Calling `start()` on an already-started bridge should either be a no-op (idempotent) or automatically dispose the existing subscription before subscribing again. No subscription should be leaked. ### Actual Behavior The first subscription handle is lost (overwritten with no reference). Domain events are delivered twice to the queue. After `stop()`, only the second subscription is disposed; the first continues delivering events indefinitely until the event bus is torn down. ### Suggested Fix ```python def start(self) -> None: """Subscribe to the event bus and begin forwarding.""" if self._subscription is not None: # Already started — idempotent guard logger.warning("a2a.event_bridge.already_started") return if hasattr(self._event_bus, "subscribe"): self._subscription = self._event_bus.subscribe(self._on_domain_event) logger.info("a2a.event_bridge.started") ``` ### Category resource-management ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:43:09 +00:00
Author
Owner

Verified — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Resource leak: EventBusBridge.start() leaks subscription handle on repeated calls. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7729
No description provided.