IncarnationClock

Builds the per-replica clock an EphemeralMap slot is stamped with, packed so that a restarted replica always out-clocks its own dead incarnation.

Why a plain counter is not enough

EphemeralMap.put keeps the higher clock per slot. A replica that restarts begins counting from zero again, so its fresh heartbeats sort below the entries its previous boot left behind, and peers keep showing the dead incarnation. TTL eviction recovers it (see the "Reconnect and clock-reset recovery" section of EphemeralMap), but only after a full window, and only for observers whose slot has actually aged out.

The packing

The clock is one Long split in two: a per-boot incarnation epoch in the high bits, and a monotonic per-boot counter in the low COUNTER_BITS. Anything a boot can reach is bounded below the next epoch's base, so a restart at a strictly greater epoch strictly dominates — by arithmetic, not by TTL timing.

The epoch must be non-decreasing across restarts of the same replica and must strictly increase on each restart. Persisted boot counters and monotonically-sourced timestamps both work; a fresh random number does not.

Serialisation

Neither function keeps state: callers hold the current clock and thread it through next. That mutation must be serialised by the caller (both shipped consumers advance the clock inside the lock that guards their board).

Properties

Link copied to clipboard
const val COUNTER_BITS: Int = 32

Low bits reserved for the per-boot counter; the incarnation epoch occupies the bits above.

Functions

Link copied to clipboard
fun base(epoch: Long): Long

The starting clock for a replica booted at epoch — the epoch shifted into the high bits, with the per-boot counter at zero.

Link copied to clipboard
fun next(current: Long): Long

The clock after current, enforcing the invariant base rests on: the per-boot counter must never carry into the epoch bits.