SoloDeadlineDetector

class SoloDeadlineDetector(minimumMembers: Int, deadline: Duration, clock: Clock, scope: CoroutineScope)

Notices when a session nobody else ever joined has been waiting long enough to give up on.

Think of a two-player table someone opens and then walks away from, or a lobby created by a misfired tap. The connection is perfectly healthy — the person is simply alone in it. After the deadline you probably want to tidy that session away rather than leave it open forever.

Give it the smallest number of members the session needs, how long to wait, a clock and a scope, then feed it the membership roster whenever it changes. It answers once, on events:

It only tells you; it never closes anything. Whether an abandoned session is closed, logged or archived is your policy, not the library's.

This detects never paired, not currently solo. The first roster at or above minimumMembers disarms it permanently, so a session that fills up and later empties out emits nothing more. Losing a peer that was present is HeartbeatPartitionDetector's job — the two are complementary, not alternatives.

In kuilt terms it is a projection of a state the contract already documents: us.tractat.kuilt.core.SeamState's KDoc blesses Woven with peers == {selfId} as a fully legitimate, well-defined state — the fabric is live and this peer is simply alone. This type adds only the deadline on top of that state. It is deliberately not a PartitionEvent variant: every PartitionEvent carries a non-null PartitionEvent.peerId, and "nobody ever came" has no peer to name.

The detector arms on construction. It is safe to call observeMembership from any thread.

Parameters

minimumMembers

the smallest membership that counts as paired, including this peer. 2 means "never paired with anyone". Must be at least 2.

deadline

how long to wait for that membership. Must be positive.

clock

supplies the SoloDeadlineEvent.at timestamps; injected so tests can fix it — never Clock.System reached for directly.

scope

the scope the deadline timer runs on. Required: a default real dispatcher would silently decouple the timer from a test's virtual clock.

Samples

val detector = SoloDeadlineDetector(
    minimumMembers = 2, // this peer plus one — "never paired"
    deadline = 5.minutes,
    clock = clock,
    scope = scope,
)
// Feed it the roster on every change.
scope.launch { link.peers.collect { detector.observeMembership(it) } }
when (detector.events.first()) {
    is SoloDeadlineEvent.NeverPaired -> closeRoom() // nobody came; reaping policy is yours
    is SoloDeadlineEvent.Paired -> Unit // someone joined in time; the detector is done
}

Constructors

Link copied to clipboard
constructor(minimumMembers: Int, deadline: Duration, clock: Clock, scope: CoroutineScope)

Properties

Link copied to clipboard
val events: SharedFlow<SoloDeadlineEvent>

The single verdict, replayed to late subscribers.

Functions

Link copied to clipboard

Feed the detector the current session membership, including this peer.