Skip to content

Comments

Fix Cancelled.__context__ leaking from other tasks#3395

Closed
Fridayai700 wants to merge 1 commit intopython-trio:mainfrom
Fridayai700:fix-cancelled-context-leak
Closed

Fix Cancelled.__context__ leaking from other tasks#3395
Fridayai700 wants to merge 1 commit intopython-trio:mainfrom
Fridayai700:fix-cancelled-context-leak

Conversation

@Fridayai700
Copy link
Contributor

Summary

Fixes #2649

When task 1 calls cancel() on a scope while handling an exception, the Cancelled exception delivered to task 2 (sleeping in that scope) incorrectly inherits task 1's exception as __context__. This is because capture(raise_cancel) is called within task 1's exception handling context, so Python automatically sets __context__ on the newly-raised Cancelled.

Before

async with trio.open_nursery() as nursery:
    cancel_scope = trio.CancelScope()
    nursery.start_soon(task2, cancel_scope)
    await trio.testing.wait_all_tasks_blocked()

    try:
        raise RuntimeError("task1 error")
    except RuntimeError:
        cancel_scope.cancel()
    # task2's Cancelled.__context__ == RuntimeError("task1 error")  ← wrong!

After

    # task2's Cancelled.__context__ is None  ← correct

Fix

Clear __context__ on the captured error in _attempt_abort before rescheduling the cancelled task. This matches the behavior of other abort pathways (deferred cancellation like IOCP/io_uring) where the Cancelled exception naturally gets __context__ = None.

The local variable is explicitly deleted to avoid creating cyclic garbage (verified by the existing test_simple_cancel_scope_usage_doesnt_create_cyclic_garbage test).

Test

Added test_cancelled_context_does_not_leak_from_other_task which:

  1. Starts task 2 sleeping in a cancel scope
  2. Has task 1 cancel the scope while handling a RuntimeError
  3. Verifies task 2's Cancelled exception has __context__ is None

The test fails without the fix (Cancelled.context == RuntimeError) and passes with it.

When task 1 calls cancel() while handling an exception, the Cancelled
exception delivered to task 2 incorrectly inherits task 1's exception as
__context__. This happens because capture(raise_cancel) is called within
task 1's exception handling context, so Python automatically sets the
__context__ on the newly-raised Cancelled.

Fix by clearing __context__ on the captured error before rescheduling.
This matches the behavior of other abort pathways (deferred cancellation
like IOCP/io_uring) where the Cancelled exception gets __context__ = None.

Fixes python-trio#2649
@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00000%. Comparing base (2fd138e) to head (9fcf542).

Additional details and impacted files
@@               Coverage Diff               @@
##                 main        #3395   +/-   ##
===============================================
  Coverage   100.00000%   100.00000%           
===============================================
  Files             128          128           
  Lines           19424        19447   +23     
  Branches         1318         1318           
===============================================
+ Hits            19424        19447   +23     
Files with missing lines Coverage Δ
src/trio/_core/_run.py 100.00000% <100.00000%> (ø)
src/trio/_core/_tests/test_run.py 100.00000% <100.00000%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@A5rocks
Copy link
Contributor

A5rocks commented Feb 18, 2026

We have a cleaner way to do this, blocked on some other PRs.

@A5rocks A5rocks closed this Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cancelled.__context__ can leak in from other tasks

2 participants