Skip to content

symfonic.capabilities.memory.owned_cycles

owned_cycles

The tasks a coordinator owns, and how they end.

Split from :mod:symfonic.capabilities.memory.napping at the 300-line budget, and the seam is a real one: that module decides whether a scope consolidates and this one owns the work once it does. A nap outlives the turn that triggered it, so somebody has to hold the task and wait for it, and that somebody is not the run.

OwnedCycles

OwnedCycles()

Every cycle a coordinator started, held until it finishes.

The construct being avoided is the shipped engine's module-level _background_tasks set: process-wide, outliving every caller, waited on by nobody. Here a task is referenced until it completes -- a task nothing holds can be garbage-collected mid-flight, and its exception is then reported against whatever happens to run next.

Source code in src/symfonic/capabilities/memory/owned_cycles.py
def __init__(self) -> None:
    self._tasks: set[asyncio.Task[Any]] = set()

pending property

pending: int

How many cycles are still in flight.

aclose async

aclose() -> None

Wait for every cycle still running. Safe to call more than once.

Failures are already logged where they happen; they are gathered rather than raised because shutdown is not the moment to fail on maintenance.

Source code in src/symfonic/capabilities/memory/owned_cycles.py
async def aclose(self) -> None:
    """Wait for every cycle still running. Safe to call more than once.

    Failures are already logged where they happen; they are gathered rather
    than raised because shutdown is not the moment to fail on maintenance.
    """
    tasks = list(self._tasks)
    if tasks:
        await asyncio.gather(*tasks, return_exceptions=True)