pub struct BatchGuard { /* private fields */ }Expand description
RAII guard returned by Core::begin_batch.
While alive, suppresses per-emit wave drains — multiple emit /
complete / error / teardown / invalidate calls coalesce into one
wave. On drop:
- Outermost guard: drains the wave (fires sinks, runs cleanup, clears in-tick).
- Nested guard (an outer
BatchGuardor an in-progress wave already owns the in-tick flag): silently no-ops.
On thread panic during the closure body, the drop path discards pending
tier-3+ delivery rather than firing sinks (avoids cascading panics).
Subscribers observe no tier-3+ delivery for the panicked wave.
State-node cache writes that already executed inside the closure are
rolled back via wave-cache snapshots — cache_of(s) returns the pre-
panic value. The atomicity guarantee covers both sink-observability and
cache state.
§Thread safety
BatchGuard is !Send by design. begin_batch claims the
per-Core in_tick flag AND the per-Core wave_owner re-entrant
mutex on the calling thread; sending the guard to another thread
and dropping it there would clear in_tick and release wave_owner
from a different thread than the one that acquired them, breaking
both the thread-local “I own the wave scope” semantic and
parking_lot::ReentrantMutex’s ownership invariant. The
_wave_guard field is !Send (ArcReentrantMutexGuard<()>); the
PhantomData<*const ()> marker is belt-and-suspenders.
use graphrefly_core::{BatchGuard, BindingBoundary, Core, DepBatch, FnId, FnResult, HandleId, NodeId};
use std::sync::Arc;
struct Stub;
impl BindingBoundary for Stub {
fn invoke_fn(&self, _: NodeId, _: FnId, _: &[DepBatch]) -> FnResult {
FnResult::Noop { tracked: None }
}
fn custom_equals(&self, _: FnId, _: HandleId, _: HandleId) -> bool { false }
fn release_handle(&self, _: HandleId) {}
}
fn requires_send<T: Send>(_: T) {}
let core = Core::new(Arc::new(Stub) as Arc<dyn BindingBoundary>);
let guard = core.begin_batch();
requires_send(guard); // <- compile_fail: BatchGuard is !Send.