---
title: "WAL consumer cursors - IWalCursorRegistry - Lattice Public API Reference"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/api/wal-consumer-cursors-iwalcursorregistry.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice/api.md?plain=1#L1239-L1284"
package: "Orleans.Lattice"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/llms-full.txt"
---
# WAL consumer cursors - `IWalCursorRegistry`

Part of [Lattice Public API Reference](../api.md).

Every consumer of a tree's write-ahead log - a leaf materialiser, a
materialised-view maintainer, a WAL subscriber, the replication shipper, or a
custom bridge - reports the highest `HybridLogicalClock` it has fully consumed
to the silo's `IWalCursorRegistry`. The WAL garbage collector
(`ILatticeWalGc.RunOnceAsync`) never trims past the minimum cursor across the
registered consumers, so a consumer that stops reporting holds the log rather
than losing entries. `AddLattice` registers the default
`InMemoryWalCursorRegistry`, which is process-local and lost on silo restart:
each consumer re-reports its cursor after a restart. `AddWalCursorRegistry(Func<IServiceProvider, IWalCursorRegistry>? factory = null)`
replaces that default with a host-supplied registry when a factory is given,
and in every case opts in to durable leaf-materialiser cursor reporting (so the
collector also respects each leaf's durable checkpoint across a restart) and
the shared WAL tailing subscriber. The durable WAL storage providers
(`AddAzureTableWalStorage`, `AddFileWalStorage`), `AddLatticeViews`, and
`AddLatticeReplication` call it for you; the storage providers and the
replication package also register the collector itself
(`AddLatticeWalGc(Func<IServiceProvider, ILatticeWalGc>? factory = null)`) and
the per-silo scheduler that runs it every `LatticeOptions.WalGcInterval`. See
[WAL](../wal.md) for the full trim predicate.

| Member | Signature | Description |
|--------|-----------|-------------|
| `ReportCursorAsync` | `Task ReportCursorAsync(string treeName, string consumerId, HybridLogicalClock cursor, CancellationToken cancellationToken = default)` | Reports the highest HLC the consumer has fully consumed. Monotonic per `(treeName, consumerId)`: a lower report is coalesced into the existing entry rather than moving the cursor back. The cursor must be greater than `HybridLogicalClock.Zero`. |
| `ReportCursorAsync` (blocked floor) | `Task ReportCursorAsync(string treeName, string consumerId, HybridLogicalClock cursor, HybridLogicalClock? blockedAtHlc, CancellationToken cancellationToken = default)` | Also reports the lowest HLC of any partially-buffered atomic batch the consumer holds; the collector never trims an entry at or above the lowest such pin. Accepts `HybridLogicalClock.Zero` as the cursor for a pin-only registration, which the cursor minimum ignores. Each report replaces the previous pin; `null` clears it. |
| `ReportCursorAsync` (vector) | `Task ReportCursorAsync(string treeName, string consumerId, HybridLogicalClock cursor, VersionVector vector, CancellationToken cancellationToken = default)` | Also reports the per-origin `VersionVector` frontier the consumer has fully applied, coalesced by pointwise maximum. |
| `ReportCursorAsync` (vector + blocked floor) | `Task ReportCursorAsync(string treeName, string consumerId, HybridLogicalClock cursor, VersionVector vector, HybridLogicalClock? blockedAtHlc, CancellationToken cancellationToken = default)` | Both of the above. |
| `UnregisterAsync` | `Task UnregisterAsync(string treeName, string consumerId, CancellationToken cancellationToken = default)` | Removes a consumer so it no longer pins the log. Idempotent. |
| `GetMinCursorAsync` | `Task<HybridLogicalClock?> GetMinCursorAsync(string treeName, CancellationToken cancellationToken = default)` | Minimum cursor across every registered consumer (pin-only registrations excluded), or `null` when none has reported. Includes cold consumers, because it feeds the trim floor. |
| `GetMinCursorForDrainLagAsync` | `Task<HybridLogicalClock?> GetMinCursorForDrainLagAsync(string treeName, long reportedAtOrAfterTicks, CancellationToken cancellationToken = default)` | Minimum cursor across consumers that reported at or after the UTC tick floor, also excluding leaf materialisers whose position is stale (see `WalDrainLagConsumerFreshness`). Feeds only the saturation drain-lag input, never the trim floor. |
| `GetCausalStableAsync` | `Task<VersionVector?> GetCausalStableAsync(string treeName, CancellationToken cancellationToken = default)` | Pointwise-minimum `VersionVector` across the consumers that reported one (an origin is kept only when every reporter names it), or `null` when none has. |
| `GetBlockedFloorAsync` | `Task<HybridLogicalClock?> GetBlockedFloorAsync(string treeName, CancellationToken cancellationToken = default)` | Minimum blocked-floor pin across the consumers currently reporting one, or `null`. |
| `SnapshotAsync` | `Task<IReadOnlyList<WalCursorSnapshot>> SnapshotAsync(string treeName, CancellationToken cancellationToken = default)` | Point-in-time copy of every registered consumer's entry, for diagnostics. |

`WalCursorSnapshot` is the `readonly record struct` `SnapshotAsync` returns:

| Member | Type | Meaning |
|--------|------|---------|
| `ConsumerId` | `string` | The reporting consumer. |
| `Cursor` | `HybridLogicalClock` | Highest HLC fully consumed; `HybridLogicalClock.Zero` for a pin-only registration. |
| `LastReportedAtTicks` | `long` | UTC ticks of the most recent report, including a re-report of an unchanged position. |
| `Vector` | `VersionVector?` | The reported per-origin frontier, or `null` for a consumer that reports HLC only. |
| `BlockedAtHlc` | `HybridLogicalClock?` | The consumer's current blocked-floor pin, or `null`. |
| `CursorAdvancedAtTicks` | `long?` (`init`) | UTC ticks of the most recent report that strictly advanced `Cursor`: `0` until the position has moved since the consumer registered, and `null` when the producing registry does not track position age. Read only by the drain-lag input, for leaf materialisers, so a leaf whose key range has seen no writes is not counted as lag; the trim floor ignores it. |

Previous: [WAL saturation back-pressure](wal-saturation-back-pressure.md). Next: [Shutdown back-pressure - LatticeShuttingDownException](shutdown-back-pressure-latticeshuttingdownexception.md). Contents: [Lattice Public API Reference](../api.md).
