mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(commons): guard RelayLatencyTracker.sweep against ConcurrentModificationException
The per-relay pending maps in RelayLatencyTracker are Collections.synchronizedMap(LinkedHashMap): individual read/write ops are thread-safe, but per the synchronizedMap javadoc iteration is NOT — callers MUST hold the map's monitor while walking its views. sweep() was iterating directly, so any network-dispatcher mutation (adding a pending REQ, receiving an OK) during a sweep would throw ConcurrentModificationException on AWT-EventQueue-0, killing the Compose renderer while coroutine work kept running. Pre-existing bug, documented in memory desktop_relay_health_cme_crash. Ordinarily "not our problem", but it's actively blocking manual T3 testing of this branch's AUTH approval banner: adding any new relay triggers a RelayHealthStore.reclassify sweep, so testers can't get a banner render in without hitting the crash. Fix it here so the branch is actually testable end-to-end. Wrap both iteration loops in synchronized(pending) blocks. Sweep is O(pending) with typically single-digit entries per relay, so the hold time is negligible and the network dispatcher just briefly waits. Reproduced during manual T3 testing 2026-07-06 when adding wss://pyramid.fiatjaf.com. Stack: RelayLatencyTracker.sweep:182 → RelayHealthStore$reclassify$flagged$1.invokeSuspend:268.
This commit is contained in:
+12
@@ -164,6 +164,18 @@ class RelayLatencyTracker(
|
||||
/**
|
||||
* Expires pending entries older than the configured TTLs and records the TTL value as the
|
||||
* sample (per the brainstorm: "punish silent relays"). Idempotent and cheap.
|
||||
*
|
||||
* The per-relay pending maps are `Collections.synchronizedMap(LinkedHashMap)` — their
|
||||
* individual reads and writes are thread-safe, but iteration is NOT: per
|
||||
* `Collections.synchronizedMap` javadoc, the caller MUST hold the returned map's
|
||||
* monitor while iterating. Directly iterating triggers a
|
||||
* `ConcurrentModificationException` when a producer thread (network dispatcher)
|
||||
* mutates the map while the sweep is walking it — reliably reproduced on macOS
|
||||
* during any relay-add on Amethyst Desktop as of 2026-07-06.
|
||||
*
|
||||
* Fix: iterate under `synchronized(pending)` blocks so the network dispatcher
|
||||
* waits until sweep releases the monitor. The sweep is O(pending), typically
|
||||
* ~single-digit entries per relay, so the hold time is negligible.
|
||||
*/
|
||||
override fun sweep(nowMs: Long) {
|
||||
// Per-relay pending maps are `Collections.synchronizedMap(LinkedHashMap)` — the
|
||||
|
||||
Reference in New Issue
Block a user