Skip to content

0006 — Trace parenting via a reset ContextVar

  • Status: accepted
  • Date: 2026-09-01

evalstand records Traces as a tree, which the implementation that inspired it does not — its traces are a flat list. The parent of a node is whatever scope is open when the call starts, tracked by a contextvars.ContextVar that is set on entry and reset via its token in a finally.

The alternatives both fail under concurrency. Inferring parentage from the async call stack is fragile and implementation-specific. A mutable stack of open nodes breaks when a call raises: the missing pop leaves the next sibling parented to a node that already closed. A token reset in finally survives both, and because contextvars copies context per task, calls fired under asyncio.gather naturally become siblings with no special handling.

Consequences

When a parent cannot be determined confidently, the node attaches to the Result root instead of guessing. An orphan at the top level is visibly incomplete; a wrongly-parented node is a plausible-looking lie, and a trace tree that quietly misrepresents which call invoked which is worse than no tree. If nesting proves unreliable under load, the fallback is a flat list — not a best-effort tree.

This is the behaviour to test first, before the showcase example exists: contextvars under asyncio.gather is where this design either holds or fails.