yggdrasil.disposable¶
disposable ¶
Disposable ¶
Bases: ABC
Transactional resource with binary open/close, with-stack nesting, and an owned-children graph.
Reopenable: subclasses inheriting from :class:Disposable are
expected to support being reopened after :meth:close. Calling
:meth:open on a closed instance must restore it to a fresh,
usable state — the same instance can cycle through
open → close → open → close any number of times. Subclass
:meth:_acquire / :meth:_release implementations must therefore
avoid one-shot state that would prevent a subsequent
:meth:_acquire from succeeding (e.g. don't null out config the
next open will need, and don't leave latch-style flags set after
teardown). A with block on a previously-closed instance must
reopen it cleanly.
open ¶
Acquire the resource and cascade into owned children.
Order:
- Run our own :meth:
_acquire(subclass body). - Flip :attr:
openedto True and mark_self_opened. -
For each owned child, in registration order:
-
If the child is already opened, just :meth:
_claimit. It stays self-opened — the existing self-open is what keeps it alive after we let go. - Otherwise, call :meth:
openon the child (which recursively cascades into ITS owned children), then clear the child's_self_openedflag so the child knows its open is parent-driven, then :meth:_claimit. Without that flag clear, the eventual :meth:_unclaimwould refuse to close — it would see "I'm self-opened, someone explicitly opened me, leave me alone."
Both branches record the child in our per-frame scratch
list so :meth:_release knows what to unclaim.
Transactional rollback: if any child's open or claim raises,
we walk back through the children we already touched (in
reverse), unclaim each, then call our own :meth:_release
with committed=False and re-raise the original exception.
From the caller's view, the open atomically either succeeded
with the whole graph live, or failed with nothing changed.
Not reentrant: raises :class:RuntimeError if already opened.
Nesting is expressed via with self: blocks, not via paired
:meth:open calls.
close ¶
Release the resource and cascade into owned children.
Order:
- If currently held open by an outside parent claim
(
_claim_count > 0) AND we are not in self-opened state, this is a no-op — the parents that opened us still need us live. (Handled inside :meth:_do_close.) - Walk our scratch list of acquired children in REVERSE
registration order; :meth:
_unclaimeach. A child whose claim count hits zero and isn't otherwise self-opened closes itself. - Run :meth:
_before_release, then :meth:_release— withcommittedreflecting the dirty bit (cleared on exception by__exit__).
Idempotent: no-op when already closed, unless force.
force=True runs teardown even when :attr:closed.
Intended for error-recovery paths where subclass state
might be inconsistent.
Does NOT touch :attr:depth — the with-stack counter
belongs to :meth:__enter__/:meth:__exit__ exclusively.
If a caller calls :meth:close inside an active with
block, the outer :meth:__exit__ will harmlessly skip the
now-no-op close on unwind.