mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-30 15:57:11 +00:00
On ARCHIVE (replay-heavy) startup the restore counters could drift: numberofracesrestoreddoneloading short and numberofracesstillloading negative (observed -1). The negative count traced to a single race whose stillLoading-- callback fired twice against one matching raceAdded +1. Every restored race arms runWhenDoneLoading twice, from two independent sites with two distinct runnables (restore path -> doneLoading++, and RaceAdditionListener.raceAdded -> stillLoading--), each meant to fire once. The old runWhenDoneLoading removed its listener inside statusChanged with no one-shot guard. Because notifyListeners snapshots the listener set under lock and then iterates the snapshot outside the lock, a removeListener happening during dispatch does not shorten an already-in-flight iteration. When a race crossed past-LOADING twice back-to-back under concurrent status pushes (loader finish plus the stopTracking teardown), a second dispatch snapshotted the listener array before the first dispatch's removeListener landed, so the callback fired on both transitions -> two decrements vs one increment -> net -1 (and a symmetric +1 over-count on doneLoading). Rewrite runWhenDoneLoading to mirror runWhenPastLoading: a settled AtomicBoolean CAS makes firing strictly one-shot per registration no matter how many times or along how many concurrent paths the transition is delivered, plus a cooperating regatta raceRemoved listener that tears down and settles-without-firing if the race is removed while still PREPARED/LOADING/ERROR (closing the companion bug6241 listener-leak class). Add TrackedRaceRunWhenDoneLoadingTest as contract tests for the single-fire / already-done / removed-before-done / fire-then-remove behaviours. Note these do not reproduce the production concurrency interleaving: setStatus dispatches statusChanged synchronously and sequentially, so single-threaded JUnit cannot deterministically recreate the snapshot/remove race; the fix rests on the settled CAS. Assisted-By: Claude (Anthropic claude-4.8-opus)