RgaGcCoordinator

class RgaGcCoordinator<V>(state: StateFlow<Rga<V>>, cutFrontier: StateFlow<CutFrontier>, delivered: StateFlow<VersionVector>, applyCompaction: (Patch<Rga<V>>) -> Unit, windowPolicy: WindowPolicy = WindowPolicy.never(), scope: CoroutineScope) : ScopedCloseable

Coordinator that drives tombstone GC (and optional history windowing) for an Rga CRDT replicated via Quilter, gated by the eviction-safe causal-stability barrier (ADR-003 addendum v3, #262).

Role

The Rga op-log grows without bound unless tombstones are periodically garbage-collected, but a tombstoned Insert(I, …) cannot be purged while a concurrent Insert(J, after=I) minted by a different author may still exist undelivered — purging I would orphan J everywhere (#262). The sound condition is causal stability, not a scalar watermark.

Quilter publishes the two version-vector quantities the barrier needs:

  • Quilter.cutFrontier — an atomically-published CutFrontier carrying both the stable cut S (min over live peers — every peer has delivered everything at-or-below it) and the frontier F = max(F_live, retainedFrontier) (the highest dot any peer, live or evicted-but-retained, has told us exists). Published together (wiring invariant W1) so a compactor never observes a half-update where F has fallen below a known-to-exist dot.

  • Quilter.deliveredLocal — this replica's own contiguous delivered version vector.

This coordinator observes cutFrontier and, on each emission, hands S, F, and the fresh delivered value to Rga.compact(stableCut, frontierMax, delivered). That method refuses GC unless the frontier is complete (delivered.dominates(F) — this replica has delivered every known-to-exist dot, so any concurrent successor of a tombstone is already visible), the tombstone is causally stable (S.contains(I.dot)), and it has no surviving local successor.

cutFrontier is republished whenever deliveredLocal or the matrix clock changes, and _deliveredLocal is updated before cutFrontier is published (see recomputeCut), so reading delivered.value on each emission is consistent with the cut that triggered it.

Loop-until-stable

On each cut emission the coordinator calls Rga.compact in a loop until it returns null. This handles the two-pass chain case: removing a tombstone may unblock a structural predecessor, making it eligible on the next pass. Quilter.apply updates state synchronously via StateFlow.update, so state.value reflects each applyCompaction before the next loop iteration — loop-until-null is therefore safe.

Each resulting RgaOp.Compact is bridged into a Patch<V>> via Rga.empty<V>().apply(compactOp) — a minimal single-op delta that any peer merges via Rga.piece, triggering the same purge on the remote op-log.

Window policy

windowPolicy may return additional ids to truncate from the visible prefix (history windowing). The default WindowPolicy.never does nothing beyond causal-stability GC.

Parameters

state

live Rga state (updated by Quilter on every incoming delta).

cutFrontier

the atomically-published causal-stability cut + frontier from Quilter.cutFrontier.

delivered

this replica's contiguous delivered VV from Quilter.deliveredLocal.

applyCompaction

called with each compaction Patch; the caller wires this to Quilter.apply so the delta propagates to all peers.

windowPolicy

optional history-windowing policy (default WindowPolicy.never).

scope

the CoroutineScope for background coroutines.

See also

Constructors

Link copied to clipboard
constructor(state: StateFlow<Rga<V>>, cutFrontier: StateFlow<CutFrontier>, delivered: StateFlow<VersionVector>, applyCompaction: (Patch<Rga<V>>) -> Unit, windowPolicy: WindowPolicy = WindowPolicy.never(), scope: CoroutineScope)

Functions

Link copied to clipboard
override fun close()