This is an automated email from the ASF dual-hosted git repository. remm pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push: new c0fff033a4 Use the container executor if available c0fff033a4 is described below commit c0fff033a4a89f5c4b8690cd5ef6ab7a30819c48 Author: remm <r...@apache.org> AuthorDate: Thu May 11 10:15:28 2023 +0200 Use the container executor if available As promised in the PR. --- .../apache/catalina/filters/RateLimitFilter.java | 9 ++- .../apache/catalina/util/LocalStrings.properties | 2 + .../apache/catalina/util/TimeBucketCounter.java | 89 ++++++++++++++-------- .../catalina/util/TestTimeBucketCounter.java | 4 +- 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/java/org/apache/catalina/filters/RateLimitFilter.java b/java/org/apache/catalina/filters/RateLimitFilter.java index 097485aef3..8172fb1ae1 100644 --- a/java/org/apache/catalina/filters/RateLimitFilter.java +++ b/java/org/apache/catalina/filters/RateLimitFilter.java @@ -18,6 +18,7 @@ package org.apache.catalina.filters; import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; @@ -31,6 +32,7 @@ import org.apache.catalina.util.TimeBucketCounter; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor; /** * <p> @@ -191,7 +193,12 @@ public class RateLimitFilter extends GenericFilter { statusMessage = param; } - bucketCounter = new TimeBucketCounter(bucketDuration); + ScheduledExecutorService executorService = + (ScheduledExecutorService) getServletContext().getAttribute(ScheduledThreadPoolExecutor.class.getName()); + if (executorService == null) { + executorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1); + } + bucketCounter = new TimeBucketCounter(bucketDuration, executorService); actualRequests = (int) Math.round(bucketCounter.getRatio() * bucketRequests); diff --git a/java/org/apache/catalina/util/LocalStrings.properties b/java/org/apache/catalina/util/LocalStrings.properties index 7b8dda0ea0..e7a3343d44 100644 --- a/java/org/apache/catalina/util/LocalStrings.properties +++ b/java/org/apache/catalina/util/LocalStrings.properties @@ -48,3 +48,5 @@ sessionIdGeneratorBase.noSHA1PRNG=The default SHA1PRNG algorithm for SecureRando sessionIdGeneratorBase.random=Exception initializing random number generator of class [{0}]. Falling back to java.secure.SecureRandom sessionIdGeneratorBase.randomAlgorithm=Exception initializing random number generator using algorithm [{0}] sessionIdGeneratorBase.randomProvider=Exception initializing random number generator using provider [{0}] + +timebucket.maintenance.error=Error processing periodic maintenance diff --git a/java/org/apache/catalina/util/TimeBucketCounter.java b/java/org/apache/catalina/util/TimeBucketCounter.java index 9a472523c7..e106e24e76 100644 --- a/java/org/apache/catalina/util/TimeBucketCounter.java +++ b/java/org/apache/catalina/util/TimeBucketCounter.java @@ -18,8 +18,16 @@ package org.apache.catalina.util; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; + /** * This class maintains a thread safe hash map that has timestamp-based buckets followed by a string for a key, and a * counter for a value. each time the increment() method is called it adds the key if it does not exist, increments its @@ -27,6 +35,9 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class TimeBucketCounter { + private static final Log log = LogFactory.getLog(TimeBucketCounter.class); + private static final StringManager sm = StringManager.getManager(TimeBucketCounter.class); + /** * Map to hold the buckets */ @@ -43,16 +54,22 @@ public class TimeBucketCounter { private final double ratio; /** - * Flag for the maintenance thread + * The future allowing control of the background processor. */ - volatile boolean isRunning = false; + private ScheduledFuture<?> maintenanceFuture; + private ScheduledFuture<?> monitorFuture; + private final ScheduledExecutorService executorService; + private final long sleeptime; /** * Creates a new TimeBucketCounter with the specified lifetime. * * @param bucketDuration duration in seconds, e.g. for 1 minute pass 60 + * @param executorService the executor service which will be used to run the maintenance */ - public TimeBucketCounter(int bucketDuration) { + public TimeBucketCounter(int bucketDuration, ScheduledExecutorService executorService) { + + this.executorService = executorService; int durationMillis = bucketDuration * 1000; @@ -68,8 +85,13 @@ public class TimeBucketCounter { this.ratio = ratioToPowerOf2(durationMillis); int cleanupsPerBucketDuration = (durationMillis >= 60_000) ? 6 : 3; - Thread mt = new MaintenanceThread(durationMillis / cleanupsPerBucketDuration); - mt.start(); + sleeptime = durationMillis / cleanupsPerBucketDuration; + + // Start our thread + if (sleeptime > 0) { + monitorFuture = executorService + .scheduleWithFixedDelay(new MaintenanceMonitor(), 0, 60, TimeUnit.SECONDS); + } } /** @@ -156,43 +178,44 @@ public class TimeBucketCounter { * Sets isRunning to false to terminate the maintenance thread. */ public void destroy() { - this.isRunning = false; - } - - /** - * This class runs a background thread to clean up old keys from the map. - */ - class MaintenanceThread extends Thread { - - final long sleeptime; - - MaintenanceThread(long sleeptime) { - super.setDaemon(true); - this.sleeptime = sleeptime; + // Stop our thread + if (monitorFuture != null) { + monitorFuture.cancel(true); + monitorFuture = null; + } + if (maintenanceFuture != null) { + maintenanceFuture.cancel(true); + maintenanceFuture = null; } + } - @SuppressWarnings("sync-override") + private class Maintenance implements Runnable { @Override - public void start() { - isRunning = true; - super.start(); + public void run() { + String currentBucketPrefix = String.valueOf(getCurrentBucketPrefix()); + ConcurrentHashMap.KeySetView<String,AtomicInteger> keys = map.keySet(); + // remove obsolete keys + keys.removeIf(k -> !k.startsWith(currentBucketPrefix)); } + } + private class MaintenanceMonitor implements Runnable { @Override public void run() { - - while (isRunning) { - String currentBucketPrefix = String.valueOf(getCurrentBucketPrefix()); - ConcurrentHashMap.KeySetView<String,AtomicInteger> keys = map.keySet(); - - // remove obsolete keys - keys.removeIf(k -> !k.startsWith(currentBucketPrefix)); - - try { - Thread.sleep(sleeptime); - } catch (InterruptedException e) { + if (sleeptime > 0 && + (maintenanceFuture == null || maintenanceFuture.isDone())) { + if (maintenanceFuture != null && maintenanceFuture.isDone()) { + // There was an error executing the scheduled task, get it and log it + try { + maintenanceFuture.get(); + } catch (InterruptedException | ExecutionException e) { + log.error(sm.getString("timebucket.maintenance.error"), e); + } } + maintenanceFuture = executorService.scheduleWithFixedDelay(new Maintenance(), sleeptime, sleeptime, + TimeUnit.MILLISECONDS); } } } + } diff --git a/test/org/apache/catalina/util/TestTimeBucketCounter.java b/test/org/apache/catalina/util/TestTimeBucketCounter.java index b1a7fd8606..43f505eb43 100644 --- a/test/org/apache/catalina/util/TestTimeBucketCounter.java +++ b/test/org/apache/catalina/util/TestTimeBucketCounter.java @@ -44,7 +44,7 @@ public class TestTimeBucketCounter { @Test public void testTimeBucketCounter() { - TimeBucketCounter tbc = new TimeBucketCounter(60); + TimeBucketCounter tbc = new TimeBucketCounter(60, new java.util.concurrent.ScheduledThreadPoolExecutor(1)); Assert.assertEquals(16, tbc.getNumBits()); Assert.assertEquals(1.092, tbc.getRatio(), DELTA); } @@ -54,7 +54,7 @@ public class TestTimeBucketCounter { long millis; int tb1, tb2; - TimeBucketCounter tbc = new TimeBucketCounter(2); + TimeBucketCounter tbc = new TimeBucketCounter(2, new java.util.concurrent.ScheduledThreadPoolExecutor(1)); tb1 = tbc.getCurrentBucketPrefix(); millis = tbc.getMillisUntilNextBucket(); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org