From 5139ccef1ee33117a3561c033005e6af33aa7c2b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 02:36:59 +0000 Subject: [PATCH] docs(commons): the cache's sorted store is what blocks commonMain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correcting yesterday's audit, which said `LargeSoftCache`'s skip-list ordering was not load-bearing and that quartz's `ConcurrentMap` could substitute. Both halves were wrong, from reading only the first half of the file. `LargeSoftCache` implements `CacheOperations` — itself `quartz/jvmAndroid`, not commonMain — for its ranged `forEach(from, to, …)`, backed by `ConcurrentSkipListMap.subMap`. All of `LargeSoftCacheAddressExt` is built on it: `filter(kindStart(kind), kindEnd(kind), …)` walks one kind's slice of the `Address` key space, and `filterIntoSet` has 28 callers. On a hash map each of those degrades to a full scan of every addressable. `quartz/linuxTest/LargeCacheRangeFallbackTest` states the same invariant from the other side: the native range overloads fall back to full scans, and that is deemed safe *because* "the range overloads have no callers outside the JVM-only LargeSoftCache". Promoting the cache makes that false. So the remaining work is not the okio sink plus a `SortedSet` signature change. It needs a sorted KMP store first — an expect/actual `LargeSoftCache` with a hand-written sorted iOS actual, or a KMP sorted-concurrent map that exists in neither the tree nor the stdlib. Recorded as a question to answer before the ordered steps, not as one of them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KkULS5SVq4GHDdoCzajKi8 --- commons/ARCHITECTURE.md | 16 ++++++---- .../2026-08-30-commons-migration-sweep.md | 32 +++++++++++++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/commons/ARCHITECTURE.md b/commons/ARCHITECTURE.md index 01aec760b2..00aaa43c71 100644 --- a/commons/ARCHITECTURE.md +++ b/commons/ARCHITECTURE.md @@ -259,12 +259,16 @@ These are intentionally *documented*, not silently tolerated. Fix opportunistica (`WeakReference` + `ConcurrentSkipListMap`), as are the two `*ListMatchingFilter` observables, `MintDirectoryIndex` and `NwcPaymentTracker`. Promoting the cache means promoting those first. - Most of it has answers already in the tree — `commons.util.WeakReference` - is an expect/actual, and quartz's `commonMain` has `ConcurrentMap`, - `ConcurrentSet` and `LargeCache` — so the genuinely new work is an okio (or - `expect`) blob sink and getting `java.util.SortedSet` out of - `EventCache.filter`'s signature, which has no common equivalent. See the - audit in `commons/plans/2026-08-30-commons-migration-sweep.md`. + The binding constraint is `LargeSoftCache`'s **sorted** store: it backs the + ranged `forEach(from, to, …)` that all of `LargeSoftCacheAddressExt` uses to + scan one kind's slice of the `Address` key space, and a hash map turns each + of those into a full scan. `quartz/linuxTest/LargeCacheRangeFallbackTest` + documents the matching invariant from the other side — the native range + overloads fall back to full scans, and that is deemed safe precisely + *because* the range callers live in the JVM-only `LargeSoftCache`. Moving + this needs a sorted KMP store first, not just the okio blob sink and the + `java.util.SortedSet` signature change. See the audit in + `commons/plans/2026-08-30-commons-migration-sweep.md`. --- diff --git a/commons/plans/2026-08-30-commons-migration-sweep.md b/commons/plans/2026-08-30-commons-migration-sweep.md index 84f9780e06..9bc05d303b 100644 --- a/commons/plans/2026-08-30-commons-migration-sweep.md +++ b/commons/plans/2026-08-30-commons-migration-sweep.md @@ -1030,11 +1030,15 @@ constraint rather than a hypothetical one. - quartz `commonMain` already ships `ConcurrentMap`, `ConcurrentSet`, `ConcurrentHashCache` and `LargeCache` — so every `ConcurrentHashMap` / `ConcurrentSkipListSet` above is a swap, not a design problem. -- `LargeSoftCache`'s skip-list ordering is **not** load-bearing: its whole - surface is `get`/`put`/`remove`/`size`/`containsKey`/`keys`/`forEach`, with - no ordered read. `ConcurrentSkipListMap` is there for lock-free concurrency, - so quartz's `ConcurrentMap` should substitute — worth confirming nothing - iterates expecting order before relying on that. +- ~~`LargeSoftCache`'s skip-list ordering is not load-bearing~~ — **wrong, + corrected 2026-09-21.** It is load-bearing, and it is the thing that stops + this migration. `LargeSoftCache` implements `CacheOperations` (which is + itself `quartz/jvmAndroid`, not commonMain) for its ranged + `forEach(from, to, …)`, backed by `ConcurrentSkipListMap.subMap`. The whole + of `LargeSoftCacheAddressExt` is built on it: `filter(kindStart(kind), + kindEnd(kind), …)` walks only one kind's slice of the `Address` key space, + and `filterIntoSet` is called 28 times. On a hash map every one of those + becomes a full scan of all addressables. - `AtomicInteger` → `kotlin.concurrent.atomics`; `BiConsumer` → a function type; the `dateFormatter` call is one log line and can go. - `androidx.collection.LruCache` in `AntiSpamFilter` is already KMP — @@ -1051,7 +1055,23 @@ constraint rather than a hypothetical one. a signature change rippling into `CacheSearch`, both observables and `LocalCacheSearchParityTest`. -**Recommended order** (bottom-up; each step is independently shippable): +**The invariant this would break.** `quartz/linuxTest/LargeCacheRangeFallbackTest` +pins the native `LargeCache` range overloads to a deliberate full-scan +fallback, and says why that is acceptable: *"the range overloads have no +callers outside the JVM-only `LargeSoftCache`"*. Promoting `LargeSoftCache` to +`commonMain` makes that statement false. Any move has to either give iOS a +genuinely sorted store or accept — explicitly, not silently — that addressable +lookups there are full scans. + +**Revised cost.** This is not the swap described above. It needs either an +`expect`/`actual` `LargeSoftCache` with a hand-written sorted iOS actual (the +`LargeCache` apple actual, for comparison, is 432 lines), or a KMP +sorted-concurrent map that does not exist in the tree or the stdlib — which +would want a `Comparable` bound on `Address` and would benefit quartz too. +Neither is a step; both are projects. + +**Recommended order** (bottom-up; each step is independently shippable), *if +the ordering question above is answered first*: 1. `LargeSoftCache` → `commonMain` on the existing `WeakReference` expect plus quartz's `ConcurrentMap`. This is the keystone — everything else is behind it.