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, thread) in_tick ownership slot AND the per-partition
wave_owner re-entrant mutex(es) on the calling thread; sending the
guard to another thread and dropping it there would clear in_tick
against the wrong thread’s slot and release the wave-owner guards
from a different thread than the one that acquired them, breaking
both the per-(Core, thread) “I own the wave scope” semantic and
parking_lot::ReentrantMutex’s ownership invariant. The wave_guards field is a SmallVec of
!Send ArcReentrantMutexGuard<()>; the PhantomData<*const ()>
marker is belt-and-suspenders.
Slice Y1 / Phase E (2026-05-08): the field migrated from a single
ArcReentrantMutexGuard (legacy Core-global wave_owner) to a
SmallVec of partition wave-owner guards. Closure-form
begin_batch acquires every current partition (serialization
point); begin_batch_for(seed) acquires only the transitively-
touched partitions (parallel for disjoint sets).
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.