BUG-HUNT: [concurrency] asyncio.CancelledError swallowed in AsyncResourceTracker.close_all #7736

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

Bug Report: Concurrency — asyncio.CancelledError Swallowed in close_all

Severity Assessment

  • Impact: Task cancellation is silently absorbed during async resource cleanup, preventing cooperative cancellation from propagating up the call stack.
  • Likelihood: High — any shutdown or test teardown that cancels a task during resource cleanup triggers this.
  • Priority: High

Location

  • File: src/cleveragents/core/async_cleanup.py
  • Function/Class: AsyncResourceTracker.close_all
  • Lines: 107–126

Description

The close_all method iterates over registered resources and awaits resource.close() for each. In the except clause on line 122, both Exception and asyncio.CancelledError are caught together and never re-raised.

In Python 3.8+, asyncio.CancelledError is a subclass of BaseException (not Exception). Catching it here and not re-raising swallows the cancellation signal. If an outer task is cancelled while close_all is mid-loop, each resource that raises CancelledError is silently absorbed, and close_all continues to completion rather than propagating the cancellation. This violates Python async best practices.

Evidence

# src/cleveragents/core/async_cleanup.py lines 107-126
for name, resource in snapshot.items():
    try:
        await asyncio.wait_for(resource.close(), timeout=timeout)
        ...
    except TimeoutError:
        self.timed_out_resources.append(name)
        ...
    except (Exception, asyncio.CancelledError):  # BUG: CancelledError absorbed
        logger.exception(
            "Error closing async resource '%s'",
            name,
        )
        # CancelledError is never re-raised here

Expected Behavior

If the task running close_all is cancelled, asyncio.CancelledError should be re-raised after logging so that cooperative cancellation propagates correctly to the caller.

Actual Behavior

asyncio.CancelledError raised during resource.close() is silently logged and discarded. The loop continues. The outer task never receives the cancellation signal, breaking cooperative multitasking.

Suggested Fix

Separate CancelledError handling and re-raise:

except asyncio.CancelledError:
    logger.warning(
        "Resource '%s' close interrupted by cancellation", name
    )
    raise  # propagate cancellation
except Exception:
    logger.exception("Error closing async resource '%s'", name)

Or if all resources must be attempted regardless, track the cancellation and re-raise after the loop.

Category

concurrency

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: Concurrency — asyncio.CancelledError Swallowed in close_all ### Severity Assessment - **Impact**: Task cancellation is silently absorbed during async resource cleanup, preventing cooperative cancellation from propagating up the call stack. - **Likelihood**: High — any shutdown or test teardown that cancels a task during resource cleanup triggers this. - **Priority**: High ### Location - **File**: `src/cleveragents/core/async_cleanup.py` - **Function/Class**: `AsyncResourceTracker.close_all` - **Lines**: 107–126 ### Description The `close_all` method iterates over registered resources and awaits `resource.close()` for each. In the `except` clause on line 122, both `Exception` and `asyncio.CancelledError` are caught together and never re-raised. In Python 3.8+, `asyncio.CancelledError` is a subclass of `BaseException` (not `Exception`). Catching it here and not re-raising swallows the cancellation signal. If an outer task is cancelled while `close_all` is mid-loop, each resource that raises `CancelledError` is silently absorbed, and `close_all` continues to completion rather than propagating the cancellation. This violates Python async best practices. ### Evidence ```python # src/cleveragents/core/async_cleanup.py lines 107-126 for name, resource in snapshot.items(): try: await asyncio.wait_for(resource.close(), timeout=timeout) ... except TimeoutError: self.timed_out_resources.append(name) ... except (Exception, asyncio.CancelledError): # BUG: CancelledError absorbed logger.exception( "Error closing async resource '%s'", name, ) # CancelledError is never re-raised here ``` ### Expected Behavior If the task running `close_all` is cancelled, `asyncio.CancelledError` should be re-raised after logging so that cooperative cancellation propagates correctly to the caller. ### Actual Behavior `asyncio.CancelledError` raised during `resource.close()` is silently logged and discarded. The loop continues. The outer task never receives the cancellation signal, breaking cooperative multitasking. ### Suggested Fix Separate CancelledError handling and re-raise: ```python except asyncio.CancelledError: logger.warning( "Resource '%s' close interrupted by cancellation", name ) raise # propagate cancellation except Exception: logger.exception("Error closing async resource '%s'", name) ``` Or if all resources must be attempted regardless, track the cancellation and re-raise after the loop. ### Category concurrency ### 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:41:58 +00:00
Author
Owner

Verified — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Concurrency bug: CancelledError swallowed in AsyncResourceTracker.close_all — prevents proper cancellation. MoSCoW: Must-have. Priority: High. --- **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#7736
No description provided.