diff --git a/src/main/java/com/mobiera/ms/commons/stats/svc/StatBuilderService.java b/src/main/java/com/mobiera/ms/commons/stats/svc/StatBuilderService.java index e33e48c..70f5809 100644 --- a/src/main/java/com/mobiera/ms/commons/stats/svc/StatBuilderService.java +++ b/src/main/java/com/mobiera/ms/commons/stats/svc/StatBuilderService.java @@ -57,7 +57,7 @@ public class StatBuilderService { private static Logger logger = Logger.getLogger(StatBuilderService.class); private static Map>>> stats; - private static boolean startedService = false; + private static volatile boolean startedService = false; public boolean isDebugEnabled() { @@ -311,8 +311,9 @@ public Stat getStat(String statClass, String entityId, StatGranularity statGranu if (raceStat == null) { dateStats.put(stateDateTime, stat); if (this.isDebugEnabled()) logger.info("getStat: CREATED: entityId: " + entityId + " stat: " + stat + " stateDateTime: " + stateDateTime + " statGranularity: " + statGranularity); - } { - logger.warn("getStat: Not CREATED: entityId: " + entityId + " stat: " + stat + " stateDateTime: " + stateDateTime + " statGranularity: " + statGranularity); + } else { + stat = raceStat; + if (this.isDebugEnabled()) logger.info("getStat: Not CREATED, using cached race winner: entityId: " + entityId + " stat: " + stat + " stateDateTime: " + stateDateTime + " statGranularity: " + statGranularity); } } @@ -336,10 +337,12 @@ public boolean isStartedService() { } - public void init(ZoneId tz) { + public synchronized void init(ZoneId tz) { this.tz = tz; - stats = new ConcurrentHashMap>>>(5); + if (stats == null) { + stats = new ConcurrentHashMap>>>(5); + } //em.find(Stat.class, "1"); } diff --git a/src/main/java/com/mobiera/ms/commons/stats/svc/StatController.java b/src/main/java/com/mobiera/ms/commons/stats/svc/StatController.java index cabbd94..5ebc6c9 100644 --- a/src/main/java/com/mobiera/ms/commons/stats/svc/StatController.java +++ b/src/main/java/com/mobiera/ms/commons/stats/svc/StatController.java @@ -78,14 +78,28 @@ public class StatController { private static final Logger logger = Logger.getLogger("StatController"); - private static boolean startedPurge = true; + private static volatile boolean startedPurge = true; - private boolean finished = false; + private volatile boolean finished = false; void onStart(@Observes StartupEvent ev) { logger.info("onStart: starting"); //startStatConsumers(); + // Initialize the timezone and the in-memory stats map BEFORE marking the + // service as started, otherwise consumer threads can observe + // startedService==true and call getStat() while stats is still null (NPE), + // since startPurgeTask() runs asynchronously on the worker pool. + if (tz == null) { + try { + tz = ZoneId.of(timezoneName); + } catch (Exception e) { + logger.error("onStart: invalid com.mobiera.ms.commons.stats.timezone: " + timezoneName); + throw(e); + } + } + statService.init(tz); + startPurgeTask(); statService.setStartedService(true); diff --git a/src/main/java/com/mobiera/ms/commons/stats/svc/StatReaderService.java b/src/main/java/com/mobiera/ms/commons/stats/svc/StatReaderService.java index 5212f79..a20e43a 100644 --- a/src/main/java/com/mobiera/ms/commons/stats/svc/StatReaderService.java +++ b/src/main/java/com/mobiera/ms/commons/stats/svc/StatReaderService.java @@ -66,15 +66,17 @@ public class StatReaderService { @ConfigProperty(name = "com.mobiera.ms.commons.stats.in.memory.past.units") Integer inMemoryPastUnits; - private static NumberFormat nf = null; + // NumberFormat is not thread-safe; the reader service is hit by multiple + // concurrent REST threads, so use a per-thread instance. + private static final ThreadLocal nf = ThreadLocal.withInitial(() -> { + NumberFormat instance = NumberFormat.getInstance(); + instance.setMaximumIntegerDigits(20); + instance.setMaximumFractionDigits(2); + return instance; + }); private NumberFormat getNf() { - if (nf == null) { - nf = NumberFormat.getInstance(); - nf.setMaximumIntegerDigits(20); - nf.setMaximumFractionDigits(2); - } - return nf; + return nf.get(); } private List buildHeader(List enums) {