From 338c9a41afacd8fef6af46ffdc790641ca06f16e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 22:52:46 +0000 Subject: [PATCH] refactor(quartz): make SeenIds single-writer, move to commonMain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop @Synchronized from add/reset/size: SeenIds is now documented as single-writer (not thread-safe). Callers dedup across concurrent relay producers by funneling events into one consumer that owns the instance — the one-consumer ingest pattern used elsewhere — which keeps a single global set, stays lock-free, and lets resize run without coordination. With the JVM-only @Synchronized gone the class is pure common Kotlin (LongArray + Hex.readLong), so it moves from the jvmAndroid source set to commonMain and is now available on every target. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015YEbdqCRPkszkGCoi89RMt --- .../com/vitorpamplona/quartz/utils/SeenIds.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename quartz/src/{jvmAndroid => commonMain}/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt (88%) diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt similarity index 88% rename from quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt index df952a74ac..51b8a1ec97 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/SeenIds.kt @@ -36,8 +36,16 @@ package com.vitorpamplona.quartz.utils * Backed by one open-addressed [LongArray] (two longs per slot, `(0,0)` = empty), so * there are NO per-entry objects and the 64-char id [String] is never retained: tens * of millions of ids cost ~16 bytes each (~1 GB at 40M) instead of the ~6 GB a - * `HashSet` of 64-char hex would. [add] is O(1) and `@Synchronized`; the lock - * is held for nanoseconds, so concurrent producers contend little. + * `HashSet` of 64-char hex would. [add] is O(1). + * + * **Not thread-safe — single-writer.** [add] mutates the table (and may resize it) and + * [reset] replaces it, so every call must come from one thread. To dedup across many + * concurrent relay producers, funnel their events into a single consumer that owns the + * SeenIds (the one-consumer ingest pattern used elsewhere in this library): that keeps + * one global set while staying single-writer, and the resize never has to coordinate. + * Giving each producer its own instance is also lock-free, but then dedups only + * *within* that producer, not across them. If you truly need concurrent writers, guard + * it yourself. * * Not unbounded-safe on its own: call [reset] between passes (or whenever the working * set should be forgotten) so a long-running process can't grow the table forever. @@ -68,7 +76,6 @@ class SeenIds( * it). A too-short/malformed id returns true — it flows through and downstream * verification drops it — rather than risk collapsing distinct ids. */ - @Synchronized fun add(idHex: String): Boolean { // Slice the first 128 bits straight to two longs via Hex's table-lookup // reader — no hex text parsing, no allocation. A string too short to slice @@ -116,13 +123,11 @@ class SeenIds( } } - @Synchronized fun reset() { allocate(1 shl INITIAL_POW2) zeroSeen = false } - @Synchronized fun size() = count + if (zeroSeen) 1 else 0 // Ids are already uniform, but avalanche the two halves so the low bits used for