mutate
Apply a local mutation expressed as a transform on the current state — the atomic read-modify-write entry point.
The read of state.value and the transform run inside the same internal reentrant lock as the apply, so the state the transform sees cannot change before its patch lands. Two concurrent mutate calls therefore serialise instead of both reading the same snapshot and max-joining each other's update away — the lost-update class that bit same-replica counter increments. transform must be pure, fast, and non-suspending: it runs inside the locked section (I/O stays outside, as everywhere).
tally.mutate { it.increment(replica, 3L) }Content copied to clipboard
Throws
if this replicator has been closed.
Samples
runTest(
StandardTestDispatcher(),
timeout = TEST_WEDGE_BACKSTOP,
) {
val loom = InMemoryLoom()
val seamAlice = loom.host(Pattern("vote-tally"))
val seamBob = loom.join(InMemoryTag("bob"))
// No manual QuiltMessage.serializer(...) wrapping needed.
val cfg = QuilterConfig(expectVirtualTime = true)
val aliceTally = Quilter(seamAlice, PNCounter.ZERO, PNCounter.serializer(), backgroundScope, config = cfg)
val bobTally = Quilter(seamBob, PNCounter.ZERO, PNCounter.serializer(), backgroundScope, config = cfg)
kotlinx.coroutines.delay(1)
// mutate removes the state.value repetition at every call site.
aliceTally.mutate { it.increment(aliceTally.replica, 3L) }
bobTally.mutate { it.decrement(bobTally.replica, 1L) }
kotlinx.coroutines.delay(10)
assertEquals(2L, aliceTally.state.value.value)
assertEquals(aliceTally.state.value.value, bobTally.state.value.value)
}Content copied to clipboard