pub struct StorageHandle { /* private fields */ }Expand description
Handle returned by attach_snapshot_storage.
D246 r3: there is no RAII Drop. The observe subscription is
torn down owner-side, synchronously, by calling
StorageHandle::detach with the owner’s &Core (the
graphrefly_core::OwnedCore borrow). StorageHandle::dispose
remains a Core-free flag flip that stops late-fire persistence
without unsubscribing (e.g. when the &Core is not in hand);
detach is the full teardown.
Graph is now Send + Sync + 'static and Core-free; the held
GraphObserveAllReactive owns a cheap Arc-clone of the graph
(no 'g borrow), so StorageHandle is 'static.
Implementations§
Source§impl StorageHandle
impl StorageHandle
Sourcepub fn dispose(&self)
pub fn dispose(&self)
Explicitly dispose: flip the per-tier disposed flag so the
in-wave observe sink stops scheduling persistence. Core-free
(no unsubscribe) — use StorageHandle::detach for the full
owner-side teardown when the &Core is available.
F3 (QA 2026-05-14): on PoisonError (a panic in the observe
sink poisoned the state mutex), recover via into_inner()
rather than silently no-op. The disposed flag still gets set
even after a sink panic, which matters for cleanup.
Sourcepub fn detach(&self, core: &Core)
pub fn detach(&self, core: &Core)
Owner-invoked, synchronous teardown (D246 r3 — replaces the
retired RAII Drop). Flips the disposed flags then detaches
the observe handle (unsubscribes every per-node sink, the
namespace-change sink, and the topology sub) via the owner’s
&Core. Idempotent: a second call is a no-op (the observe
handle is taken on first call).
Sourcepub fn flush_all(&self) -> Result<(), StorageError>
pub fn flush_all(&self) -> Result<(), StorageError>
Drain pending writes on all tiers (D171).
When a tier is configured with debounce_ms > 0,
attach_snapshot_storage’s observe sink writes via the
tier’s save() but skips the inline flush(). Callers
invoke this method — typically from a binding-side reactive
timer subgraph (graphrefly_operators::temporal::interval,
shipped in Slice T) — to commit the buffered writes to their
backends. With debounce_ms == 0, the observe sink already
force-flushes inline; calling this method is then a no-op
because the tier’s pending buffers are empty.
Returns the first error encountered, if any. Successful tiers in the same call still flush; the error is surfaced for the caller’s diagnostics.
§Lock discipline (N3, QA 2026-05-14)
The state mutex is not held across tier.flush() calls.
Holding it across blocking I/O would (a) serialize the
reactive graph against backend latency (every observe-sink
fire would wait on flush completion), and (b) deadlock if a
caller invokes flush_all from inside an on_error callback
(the observe sink already holds state.lock() when it
invokes on_error). The implementation snapshots the per-
tier flush requests under the lock, drops the lock, then
runs the flushes in sequence. After each flush, a brief
re-lock applies any per-tier state mutations (none today —
flush() doesn’t return new state).
§Errors
Returns the first StorageError encountered. Subsequent
errors are dropped (acceptable v1; the registered
on_error callback fires per error in the observe sink path
and is the canonical multi-error reporting surface).
F3: recovers from a poisoned state mutex via into_inner
rather than silently returning Ok.