pub struct BatchGuard<'a> { /* 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
one-Core-per-OS-thread in_tick ownership slot (D252) on the
calling thread; sending the guard to another thread and dropping it
there would clear in_tick against the wrong thread’s slot,
breaking the “I own the wave scope” semantic. D246/S2c: single-owner
⇒ the §7 per-partition wave_owner re-entrant mutex(es) are
deleted; !Send is now enforced solely by the
PhantomData<*const ()> marker.
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.