From a4ca7738ef9fb7ea4079e957bf4b76ecbe510a36 Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Wed, 8 Mar 2023 11:12:12 -0500 Subject: [PATCH 1/2] Use a deep copy of query stats whose values won't change during sorting. Fixes https://bz.apache.org/bugzilla/show_bug.cgi?id=58489 --- .../pool/interceptor/SlowQueryReport.java | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java index 4198d3759e29..86a2a1a90576 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java @@ -222,19 +222,54 @@ protected QueryStats getQueryStats(String sql) { return qs; } + static class MiniQueryStats { + public final QueryStats queryStats; + public final long lastInvocation; + + public MiniQueryStats(QueryStats queryStats) { + this.queryStats = queryStats; + this.lastInvocation = queryStats.lastInvocation; + } + } + + static class MiniQueryStatsComparator implements Comparator + { + @Override + public int compare(MiniQueryStats stats1, MiniQueryStats stats2) { + return Long.compare(handleZero(stats1.lastInvocation), + handleZero(stats2.lastInvocation)); + } + + private static long handleZero(long value) { + return value == 0 ? Long.MAX_VALUE : value; + } + } + + private MiniQueryStatsComparator miniQueryStatsComparator = new MiniQueryStatsComparator(); + /** * Sort QueryStats by last invocation time * @param queries The queries map */ protected void removeOldest(ConcurrentHashMap queries) { - ArrayList list = new ArrayList<>(queries.values()); - Collections.sort(list, queryStatsComparator); + // Make a defensive deep-copy of the query stats list to prevent + // concurrent changes to the lastModified member during list-sort + ArrayList list = new ArrayList<>(queries.size()); + for(QueryStats stats : queries.values()) + list.add(new MiniQueryStats(stats)); + + Collections.sort(list, miniQueryStatsComparator); int removeIndex = 0; while (queries.size() > maxQueries) { - String sql = list.get(removeIndex).getQuery(); - queries.remove(sql); - if (log.isDebugEnabled()) { - log.debug("Removing slow query, capacity reached:"+sql); + MiniQueryStats mqs = list.get(removeIndex); + // Check to see if the lastInvocation has been updated since we + // took our snapshot. If the timestamps disagree, it means + // that this item is no longer the oldest (and it likely now + // one of the newest). + if(mqs.lastInvocation == mqs.queryStats.lastInvocation) { + String sql = mqs.queryStats.query; + queries.remove(sql); + if (log.isDebugEnabled()) log.debug("Removing slow query, capacity reached:"+sql); } removeIndex++; } From f237d05c2020662e31b22b9adf97dd3817685b25 Mon Sep 17 00:00:00 2001 From: Christopher Schultz Date: Thu, 9 Mar 2023 09:04:21 -0500 Subject: [PATCH 2/2] Match coding style. --- .../apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java index 86a2a1a90576..2410e07278a0 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReport.java @@ -255,8 +255,9 @@ protected void removeOldest(ConcurrentHashMap queries) { // Make a defensive deep-copy of the query stats list to prevent // concurrent changes to the lastModified member during list-sort ArrayList list = new ArrayList<>(queries.size()); - for(QueryStats stats : queries.values()) + for(QueryStats stats : queries.values()) { list.add(new MiniQueryStats(stats)); + } Collections.sort(list, miniQueryStatsComparator); int removeIndex = 0;