diff --git a/src/main/java/com/mobiera/ms/commons/stats/res/s/StatServerResource.java b/src/main/java/com/mobiera/ms/commons/stats/res/s/StatServerResource.java index e6e791d..f4ff651 100644 --- a/src/main/java/com/mobiera/ms/commons/stats/res/s/StatServerResource.java +++ b/src/main/java/com/mobiera/ms/commons/stats/res/s/StatServerResource.java @@ -81,13 +81,10 @@ public Response getStatViewRequest(GetStatView request) throws JsonProcessingExc Response.Status status = Response.Status.BAD_REQUEST; - request.setStatGranularity(this.computeGranularity(request.getFrom(), request.getTo())); - if ((request.getFrom() == null) || (request.getTo() == null) || (request.getStatClass() == null) || (request.getStatEnums() == null) || - (request.getStatGranularity() == null) || (request.getStatResultType() == null) || (request.getStatEnums().size() == 0) ) { @@ -96,7 +93,9 @@ public Response getStatViewRequest(GetStatView request) throws JsonProcessingExc } else { - + // Derive granularity only after from/to are validated; computeGranularity + // dereferences both, so doing it first would NPE (500) on a bad request. + request.setStatGranularity(this.computeGranularity(request.getFrom(), request.getTo())); try { answer = statReaderService.getStatViewVO(request.getFrom(), request.getTo(), request.getEntityIds()==null?new ArrayList(0):request.getEntityIds(), @@ -122,20 +121,21 @@ public Response getStatViewRequest(GetStatView request) throws JsonProcessingExc @Consumes(MediaType.APPLICATION_JSON) public Response getCompareStatViewRequest(CompareStatView request) throws JsonProcessingException, InterruptedException { - request.setStatGranularity(this.computeGranularity(request.getFrom(), request.getTo())); StatView answer = null; Response.Status status = Response.Status.BAD_REQUEST; if ((request.getFrom() == null) || (request.getTo() == null) || (request.getKpis() == null) || - (request.getStatGranularity() == null) || (request.getStatResultType() == null) || (request.getKpis().size() == 0) ) { status = Status.BAD_REQUEST; } else { + // Derive granularity only after from/to are validated; computeGranularity + // dereferences both, so doing it first would NPE (500) on a bad request. + request.setStatGranularity(this.computeGranularity(request.getFrom(), request.getTo())); try { answer = statReaderService.getCompareStatViewVO(request.getFrom(), request.getTo(), request.getKpis(), 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 70f5809..0c589a5 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 @@ -431,6 +431,13 @@ public void flushStats(boolean shutdown) { } + if (stat == null) { + synchronized (dateStats) { + dateStats.remove(currentDateTime); + } + continue; + } + if (shutdown) { try { Object statLockObj = this.getLockObj(stat.getLockObjId()); @@ -483,12 +490,13 @@ public void flushStats(boolean shutdown) { Object statLockObj = this.getLockObj(stat.getLockObjId()); synchronized (statLockObj) { stat = treatStatFlush(stat); - - } - - synchronized (dateStats) { - dateStats.put(currentDateTime, stat); - + // Replace the cached instance while still holding the + // per-bucket lock so a concurrent increment cannot apply + // to the pre-merge instance and then be discarded (lost + // update) when the merged instance overwrites it. + synchronized (dateStats) { + dateStats.put(currentDateTime, stat); + } } } catch (Exception e) { @@ -547,8 +555,22 @@ public void flushStats(String statClass, String entityId) { synchronized (dateStats) { stat = dateStats.get(currentDateTime); + } + + if (stat == null) { + continue; + } + + // Use the same per-bucket lock discipline as the periodic + // flush so on-demand flushes and concurrent increments are + // mutually exclusive, and to keep a consistent + // statLockObj -> dateStats lock ordering (no deadlock). + Object statLockObj = this.getLockObj(stat.getLockObjId()); + synchronized (statLockObj) { stat = treatStatFlush(stat); - dateStats.put(currentDateTime, stat); + synchronized (dateStats) { + dateStats.put(currentDateTime, stat); + } }