diff --git a/fleetd/src/main/java/dev/ltms/fleet/health/FleetHealthMonitor.java b/fleetd/src/main/java/dev/ltms/fleet/health/FleetHealthMonitor.java index 6ec2e49..cfa11b3 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/health/FleetHealthMonitor.java +++ b/fleetd/src/main/java/dev/ltms/fleet/health/FleetHealthMonitor.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -46,7 +47,16 @@ public final class FleetHealthMonitor { private final long workingSuspectAfterNanos; private final BiConsumer failTarget; private final Map priors = new HashMap<>(); - private final Map states = new HashMap<>(); + /** + * The live classification per member, and the only one of this class's three maps that more + * than one scheduler task touches. {@code tick} writes it (and prunes it to the roster); + * fleetd #280's delayed {@link #recheckTerminalTarget} reads it from its own separate scheduled + * task. Both run on the single-threaded scheduler {@code Fleetd} passes in today, so they are + * serialised — but nothing in this class enforces that, and an unsynchronised {@link HashMap} + * read racing a resize can spin a CPU forever rather than fail visibly. {@code priors} and + * {@code orphanStreaks} stay plain maps because {@code tick} is still their only toucher. + */ + private final Map states = new ConcurrentHashMap<>(); /** * CB-643: consecutive ticks on which a target looked like an orphaned delegation. The fact * {@link MessageService#hasOrphanedDelegation} reports is a true snapshot, but it can read true