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

Python AsyncExitStack: unwind acquired resources after a later open fails

Last updated: 1 Oct 20265 min read
tutorial
IntermediateBy AITrove Editorial

AsyncExitStack closes entered async contexts in reverse order when a later acquisition fails.

Download Python source kit

Operation contract

A local receipt workflow acquires a database lease and then a queue lease. The third acquisition rejects its input before entering. The stack releases the two acquired leases in reverse order, and the original ValueError remains visible to the caller.

Failure boundary

An async exit callback can itself fail or be cancelled; a real owner must decide how to retain cleanup failures and bound shutdown. This fixture records only in-memory events and does not open a database, broker, or network connection.

Working program

python
import asyncio
from contextlib import AsyncExitStack, asynccontextmanager

events = []

@asynccontextmanager
async def receipt_lease(name, *, reject=False):
    if reject:
        raise ValueError("lease rejected")
    events.append("acquired:" + name)
    try:
        yield name
    finally:
        events.append("released:" + name)

async def prepare_receipt():
    try:
        async with AsyncExitStack() as resources:
            await resources.enter_async_context(receipt_lease("database"))
            await resources.enter_async_context(receipt_lease("queue"))
            await resources.enter_async_context(receipt_lease("export", reject=True))
    except ValueError:
        print("acquisition_rejected", True)
    print("events", events)

asyncio.run(prepare_receipt())

Output

Output
acquisition_rejected True
events ['acquired:database', 'acquired:queue', 'released:queue', 'released:database']

Costs and limits

Each entered context adds one cleanup record, so retained stack state is linear in acquired resources. Unwinding invokes every exit callback; a slow close extends the failure path.

Common Mistakes

  • Register cleanup as each acquisition succeeds, not only after all opens complete.
  • Reverse release order matters when later resources depend on earlier ones.
  • An in-memory unwind does not prove cancellation-safe cleanup of external services.

Connected lessons

Test this contract.

python
async-exit-stack-partial-acquisition
Storage details