MuxServerLoom

class MuxServerLoom(source: ConnectionSource, scope: CoroutineScope, val selfId: PeerId, authorizer: RoomAuthorizer, dispatcher: CoroutineContext = requireNotNull(scope.coroutineContext[ContinuationInterceptor]) { "MuxServerLoom scope must have a ContinuationInterceptor (dispatcher)" }, random: Random = Random.Default, handshakeTimeout: Duration = 10.seconds) : Loom, ScopedCloseable

A server-side Loom that provides structural per-room isolation over a shared ConnectionSource.

Each accepted Connection is handshaked into a 2-peer Seam and read exactly once; inbound frames are demultiplexed by channel name (the NamedFrame wire header) and pushed into the matching RoomHubSeam. host for a given room name returns that RoomHubSeam — a server-centred star Seam that forwards broadcasts only to connections admitted to that room. A connection joins a room when its first frame arrives on that room's channel AND authorizer grants admission. A non-member is never in the fanout list — isolation is by construction, not by runtime guard.

Why single-collection-and-demux

Routing per-room registration through a NamedMux channel view (a replay-0 shareIn plus a per-view pipe) makes registration depend on subscription timing — a frame can arrive before the room's collector subscribes and be lost, leaving a peer permanently unregistered. Reading each connection's seam once and pushing demuxed frames into a bounded Spool inside each room removes that race: the buffered spool retains a frame delivered before the room's consumer subscribes, so registration and fanout are deterministic under virtual time.

Usage

val serverLoom = MuxServerLoom(
source = source,
scope = scope,
selfId = PeerId("server"),
authorizer = RoomAuthorizer { peer, tag -> sessionStore.isAdmitted(peer, tag) },
)
val room7 = serverLoom.host(Pattern("table-7"))
val room9 = serverLoom.host(Pattern("table-9"))

Reconnect / resume (server side)

Membership is keyed by PeerId, not by the underlying connection. When a connection with a previously-seen PeerId is accepted (a reconnect over a fresh transport with the same identity), its demuxed frames re-register it into whatever room its tags name. Because RoomHubSeam keys its registration map by PeerId and compares the outbound handle by identity, the returning connection lands back in exactly the rooms it re-announces, and the dropped connection's later teardown does not evict the resumed membership. Client-side resume (re-emitting tags after a drop) lives in a separate concern; this Loom handles only the server-side re-association.

join throws UnsupportedOperationException — this is a server-only Loom.

Lifecycle

A ScopedCloseable: the accept loop and every per-connection read/watch pump run in an owned child scope whose job is a child of scope's job. Cancelling scope therefore stops every pump, and close tears the loom down explicitly — it halts the accept loop, cancels all per-connection pumps, and (via each read loop's teardown) deregisters the connection from the rooms it joined. close is idempotent. It does not tear the hosted RoomHubSeams or the per-connection mesh seams: those have independent lifecycles owned by their consumers and by the source/connection provider respectively.

Thread safety

connRecords, rooms, and launchedJobs are guarded by lock. Suspend calls are always outside the lock.

Parameters

source

accept source for incoming client connections.

scope

parent scope for the loom. The owned pump scope (accept loop + per-connection read loops + tracking coroutines) is a child of it, so cancelling scope — or calling close — stops every pump.

selfId

this server's own PeerId.

authorizer

required authorization policy for per-room membership. Invoked on the first inbound frame per (connection, room) pair; a false return structurally excludes the connection from that room. Use RoomAuthorizer.AllowAll for open-access servers. Required (no default) per the "optional ≠ tuning" rule — absent it the gate would be silently disabled.

dispatcher

coroutine context for mesh-seam link loops. Defaults to the interceptor from scope so virtual-time tests share the test dispatcher automatically.

random

seeded Random for mesh-seam nonce generation.

handshakeTimeout

ceiling on a single accepted connection's handshake+admit; a connection that connects but never completes its MeshHello handshake is abandoned and closed after this, so it can never wedge the accept loop. Each accepted connection is admitted concurrently in its own child coroutine (all shared state is lock-guarded), so a slow handshake never starves later connections either.

Constructors

Link copied to clipboard
constructor(source: ConnectionSource, scope: CoroutineScope, selfId: PeerId, authorizer: RoomAuthorizer, dispatcher: CoroutineContext = requireNotNull(scope.coroutineContext[ContinuationInterceptor]) { "MuxServerLoom scope must have a ContinuationInterceptor (dispatcher)" }, random: Random = Random.Default, handshakeTimeout: Duration = 10.seconds)

Properties

Link copied to clipboard
val connectedPeers: StateFlow<Set<PeerId>>

The set of remote PeerIds with a live connection to this server — every peer whose link has been accepted and handshaked and whose read pump has not yet torn down. A peer appears the moment its connection is admitted and disappears when its link tears (or on close).

Link copied to clipboard

Functions

Link copied to clipboard

Whether this fabric can be attempted now — the availability half of capability. Derived; do not override.

Link copied to clipboard

This fabric's role(s) and whether it can be attempted now. The single capability primitive — override this, not availability. Default: a roleless FabricAvailability.Available.

Link copied to clipboard
override fun close()

Cancels all background coroutines owned by this instance. Idempotent and thread-safe — safe to call multiple times, from any thread, concurrently.

Link copied to clipboard
open suspend fun host(pattern: Pattern): Seam

Host / start a new session.

Link copied to clipboard
open suspend fun join(tag: Tag): Seam

Join an existing session. The advertisement carries enough info to reach the existing peer set.

Link copied to clipboard
open suspend override fun weave(rendezvous: Rendezvous): Seam

Establish a Seam according to rendezvous — either host a new session or join an existing one.