Asyncio shield lets an inner task continue when cancellation interrupts the caller awaiting that shield.
Python asyncio shield: caller cancellation does not transfer task ownership
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
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
caller cancelled
inner pending: True
inner result: storedCosts 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.
