perf(quartz): make the negentropy idle watchdog allocation-free

IdleClock.bump() is called for every message the relay sends — the connection
listener bumps it per event, so a multi-million-event download bumped it millions
of times. It stored a ValueTimeMark into an AtomicReference, and since the value
class boxes when used as a generic type argument, every bump allocated a heap
object. That is needless GC pressure on the hottest path (and battery/jank on
Android).

Replace it with a single monotonic base mark taken once (stored unboxed) plus a
@Volatile Long of nanos-since-start updated on each bump — zero allocation per
bump, and only visibility (not atomicity) is needed since each relay's bumps come
from its single reader thread and the driver only reads. Behavior is unchanged;
negentropy + concurrency suites pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmSyzdmKyiz3pPxUZ8Mg8Z
This commit is contained in:
Claude
2026-07-01 14:18:31 +00:00
parent 55e945a5ed
commit 322e33678a
@@ -42,8 +42,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.Volatile
import kotlin.coroutines.coroutineContext
import kotlin.math.min
import kotlin.time.TimeSource
@@ -680,18 +679,26 @@ private const val DEFAULT_DOWNLOAD_IDLE_MS = 60_000L
/**
* Monotonic "last activity" marker for the idle watchdog. [bump] on every sign of
* life from the relay; [elapsedMs] reports the silence since the last bump. Thread
* safe: bumped from relay reader threads, read from the driver coroutine.
* life from the relay; [elapsedMs] reports the silence since the last bump.
*
* [bump] is on the per-event hot path (the connection listener bumps for every
* message the relay sends — millions during a large download), so it must not
* allocate: a single [start] mark is taken once (unboxed field) and each bump only
* writes a `Long` of nanos-since-start into a `@Volatile` field. Reader threads
* write, the driver coroutine reads — visibility is all we need, so a plain volatile
* Long beats boxing a `ValueTimeMark` into an `AtomicReference` on every event.
*/
@OptIn(ExperimentalAtomicApi::class)
private class IdleClock {
private val last = AtomicReference(TimeSource.Monotonic.markNow())
private val start = TimeSource.Monotonic.markNow()
@Volatile
private var lastNanos = 0L
fun bump() {
last.store(TimeSource.Monotonic.markNow())
lastNanos = start.elapsedNow().inWholeNanoseconds
}
fun elapsedMs(): Long = last.load().elapsedNow().inWholeMilliseconds
fun elapsedMs(): Long = (start.elapsedNow().inWholeNanoseconds - lastNanos) / 1_000_000
}
/**