EphemeralMapTracker

class EphemeralMapTracker<V>(val ttlMs: Long, clock: () -> Long = defaultClock())

A stateful wrapper around EphemeralMap that stamps local receive times and drives TTL eviction.

Responsibilities

  • Maintains a mutable EphemeralMap state by merging inbound EphemeralMap updates via received.

  • Stamps a local receive time (via clock) whenever a replica's entry advances to a higher clock — i.e. only real updates reset the TTL, not stale re-deliveries.

  • Surfaces live: the set of entries not yet expired and not departed.

Update contract — feed received author-fresh deltas

Every entry handed to received should be one its author just published. That is what makes "an update arrived" mean "that replica is alive", which is the whole basis of TTL presence.

Relaying is the thing to avoid: re-sending another replica's slot, echoing a merged map back, or exchanging snapshot wholesale (as generic anti-entropy does) delivers entries whose author may be long gone.

received guards what a guard can reach. An inbound entry identical to the one already held is inert, so echoing an unchanged merged map cannot resurrect anyone; and eviction never admits an entry the standing one already dominates, so a departed replica stays departed and a relayed departure cannot re-open a slot (#1675). What remains is genuinely undecidable: a relayed presence entry differing from an expired presence slot looks exactly like a restarted replica's first heartbeat, and is admitted as one — re-stamping the TTL and showing a dead replica live for another window, once per such delivery.

That residue is why there are two channels. If a delivery is not a heartbeat from the replica it names, hand it to relayed instead: it joins the state without ever evicting, and stamps only entries that genuinely advance, so the undecidable case never arises. received for author-fresh deltas, relayed for everything else.

Clock contract

clock is a () -> Long that returns the current local monotonic time in milliseconds. The production default is kotlin.time.TimeSource.Monotonic. Tests inject a controlled counter so eviction can be driven deterministically without wall-clock dependencies.

Parameters

ttlMs

expiry window in milliseconds. An entry is considered expired when now - receiveTime >= ttlMs. The boundary is exclusive: exactly at ttlMs ms the entry is expired.

clock

injectable monotonic time source (milliseconds).

Type Parameters

V

the presence value type.

Constructors

Link copied to clipboard
constructor(ttlMs: Long, clock: () -> Long = defaultClock())

Properties

Link copied to clipboard
val ttlMs: Long

Functions

Link copied to clipboard
fun live(): Map<ReplicaId, V>

Returns the current set of live entries: non-departed, non-expired replicas mapped to their values.

Link copied to clipboard
fun received(update: EphemeralMap<V>)

Merge an inbound update into the local state.

Link copied to clipboard
fun relayed(update: EphemeralMap<V>)

Merge state this replica did not receive from its author — an anti-entropy round, a full-state exchange, a forwarded slot, a merged map echoed back. The compliant way to feed a tracker anything received must not be given.

Link copied to clipboard

The current merged CRDT state (all entries, including departed/stale).