Skip to content

← Under the Hood: Objects, Memory, Speed step 3 of 35

Medium Primitives

The __exit__ contract: return the resource, propagate the exception

Fix a context manager that borrows from a pool. It typechecks today. It also swallows every exception raised inside its block and double-checks-out on a nested with.

ResourcePool is provided and must not be changed. It hands out names r0, r1, … from a free list, appends out:<name> on checkout and in:<name> on checkin, and exposes available.

Repair PooledResource so that:

  • __enter__ returns Self, checking a resource out of the pool.
  • __exit__ returns the resource on every path — normal exit and exception alike — and is annotated -> None so it cannot suppress the exception passing through it.
  • It is re-entrant: entering the same PooledResource object twice checks out once and checks in once, when the outermost block exits.
  • A PooledResource that is constructed and never entered touches nothing. Nothing may rely on __del__.

solve(pool_size, actions) drives a scenario and returns what happened:

def solve(pool_size: int, actions: list[str]) -> dict[str, object]: ...

Each action is one of:

action meaning
"ok" enter, log use:<name>, exit normally
"raise" enter, log use:<name>, raise ValueError("body failed") inside the block, catch it outside
"nested" enter the same object twice; log use:<name> inside, still:<name> between the two exits
"orphan" construct a PooledResource, never enter it, log orphan

The return value is {"log": <the pool's log>, "available": <free count>, "escaped": ["ValueError:body failed", ...]}, where escaped records the exceptions that made it out of their with block.

Why this is the shape of a real outage. A __exit__ annotated -> bool and ending in return True reads like “yes, cleanup succeeded”. It actually means “swallow whatever went wrong in there”. The annotation and the body agree, so mypy --strict is silent, and the service reports success on requests that failed. Annotate -> None and a stray return True becomes a type error — the one place where choosing the narrower return type is what buys you the check.

The re-entrancy requirement is the second half: a helper that opens a with on a resource its caller already holds is normal, and a naive implementation checks the resource out twice and returns it twice, quietly corrupting the free list.

Your submission must pass mypy --strict.