Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python asyncio shield: caller cancellation does not transfer task ownership

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

Asyncio shield lets an inner task continue when cancellation interrupts the caller awaiting that shield.

Download Python source kit

Operation contract

The caller creates and retains the persistence task, waits until its owned fixture starts, and then the controller cancels the caller. The caller still receives CancelledError. The inner task waits on a separate event and remains pending until the controller permits completion. The controller finally awaits it, so completion and cleanup have an explicit owner. Events establish ordering without timing-sensitive sleeps.

Failure and ownership boundary

Shield is not a general cancellation ban. The inner task can still fail or be cancelled directly, and continuing work may finish after a request has gone away. Decide who may publish its result. Python asyncio timeout: cancellation and cleanup ownership, Python asyncio TaskGroup: cancel sibling work and retain failure evidence and Python SQLite outbox project: commit a receipt and its pending event together concern separate scopes; shield alone does not make a side effect durable.

Working program

python
import asyncio

async def trace():
    started, release = asyncio.Event(), asyncio.Event()
    retained = []
    async def persist():
        started.set()
        await release.wait()
        return "stored"
    async def caller():
        task = asyncio.create_task(persist())
        retained.append(task)
        try:
            await asyncio.shield(task)
        except asyncio.CancelledError:
            print("caller cancelled")
            raise
    request = asyncio.create_task(caller())
    await started.wait()
    request.cancel()
    try:
        await request
    except asyncio.CancelledError:
        pass
    print("inner pending:", not retained[0].done())
    release.set()
    print("inner result:", await retained[0])

asyncio.run(trace())

Output

Output
caller cancelled
inner pending: True
inner result: stored

Costs and limits

Each retained task owns its suspended coroutine state. Unbounded background task creation can retain memory and work after clients disconnect. This fixture owns exactly one inner task and awaits its terminal result; a service needs capacity, shutdown and failure-reporting policies.

Common Mistakes

  • The cancelled caller still receives CancelledError.
  • Retain and await work that continues after its original caller exits.

Connected lessons

Python asyncio timeout: cancellation and cleanup ownership, Python asyncio TaskGroup: cancel sibling work and retain failure evidence, Python cancellation review: release a permit before propagating task cancellation.

python
async-shield-ownership
Storage details