This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 93c0819a4297a6f3cc4be3d9aefe99d9b536b7ad Author: Mark Thomas <ma...@apache.org> AuthorDate: Fri Mar 7 13:51:00 2025 +0000 Refactor RateLimiter FastRateLimiter for extension Preparation for #794 --- java/org/apache/catalina/util/FastRateLimiter.java | 76 +---------- java/org/apache/catalina/util/RateLimiter.java | 7 +- java/org/apache/catalina/util/RateLimiterBase.java | 145 +++++++++++++++++++++ 3 files changed, 154 insertions(+), 74 deletions(-) diff --git a/java/org/apache/catalina/util/FastRateLimiter.java b/java/org/apache/catalina/util/FastRateLimiter.java index 486a133a64..17544c5d28 100644 --- a/java/org/apache/catalina/util/FastRateLimiter.java +++ b/java/org/apache/catalina/util/FastRateLimiter.java @@ -14,92 +14,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.catalina.util; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicInteger; - -import jakarta.servlet.FilterConfig; - -import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor; /** * A RateLimiter that compromises accuracy for speed in order to provide maximum throughput. */ -public class FastRateLimiter implements RateLimiter { - - private static AtomicInteger index = new AtomicInteger(); - - TimeBucketCounter bucketCounter; - - int duration; - - int requests; - - int actualRequests; - - int actualDuration; - - // Initial policy name can be rewritten by setPolicyName() - private String policyName = "fast-" + index.incrementAndGet(); - - @Override - public String getPolicyName() { - return policyName; - } - - @Override - public void setPolicyName(String name) { - this.policyName = name; - } +public class FastRateLimiter extends RateLimiterBase { @Override - public int getDuration() { - return actualDuration; + protected String getDefaultPolicyName() { + return "fast"; } - @Override - public void setDuration(int duration) { - this.duration = duration; - } - - @Override - public int getRequests() { - return actualRequests; - } - - @Override - public void setRequests(int requests) { - this.requests = requests; - } @Override - public int increment(String ipAddress) { - return bucketCounter.increment(ipAddress); + protected TimeBucketCounterBase newCounterInstance(int duration, ScheduledExecutorService executorService) { + return new TimeBucketCounter(duration, executorService); } - @Override - public void destroy() { - bucketCounter.destroy(); - } - - @Override - public void setFilterConfig(FilterConfig filterConfig) { - - ScheduledExecutorService executorService = (ScheduledExecutorService) filterConfig.getServletContext() - .getAttribute(ScheduledThreadPoolExecutor.class.getName()); - - if (executorService == null) { - executorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1); - } - - bucketCounter = new TimeBucketCounter(duration, executorService); - actualRequests = (int) Math.round(bucketCounter.getRatio() * requests); - actualDuration = bucketCounter.getActualDuration() / 1000; - } public TimeBucketCounter getBucketCounter() { - return bucketCounter; + return (TimeBucketCounter) bucketCounter; } } diff --git a/java/org/apache/catalina/util/RateLimiter.java b/java/org/apache/catalina/util/RateLimiter.java index bdd8b27736..72effc8498 100644 --- a/java/org/apache/catalina/util/RateLimiter.java +++ b/java/org/apache/catalina/util/RateLimiter.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.catalina.util; import jakarta.servlet.FilterConfig; @@ -46,13 +45,13 @@ public interface RateLimiter { void setRequests(int requests); /** - * Increments the number of requests by the given ipAddress in the current time window. + * Increments the number of requests by the given identifier in the current time window. * - * @param ipAddress the ip address + * @param identifier the identifier for which the number of associated requests should be incremented * * @return the new value after incrementing */ - int increment(String ipAddress); + int increment(String identifier); /** * Cleanup no longer needed resources. diff --git a/java/org/apache/catalina/util/RateLimiterBase.java b/java/org/apache/catalina/util/RateLimiterBase.java new file mode 100644 index 0000000000..1f4c699462 --- /dev/null +++ b/java/org/apache/catalina/util/RateLimiterBase.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.catalina.util; + +import java.util.Objects; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.servlet.FilterConfig; + +/** + * Base implementation of {@link RateLimiter}, provides runtime data maintenance mechanism monitor. + */ +public abstract class RateLimiterBase implements RateLimiter { + + private static final AtomicInteger index = new AtomicInteger(); + + TimeBucketCounterBase bucketCounter; + + int requests; + int actualRequests; + + int duration; + int actualDuration; + + // Initial policy name can be rewritten by setPolicyName() + private String policyName = null; + + /* + * The self-owned utility executor, will be instantiated only when ScheduledThreadPoolExecutor is absent during + * filter configure phase. + */ + private ScheduledThreadPoolExecutor internalExecutorService = null; + + /** + * If policy name has not been specified, the first call of {@link #getPolicyName()} returns a auto-generated policy + * name using the default policy name as prefix and followed by auto-increase index. + * + * @return default policy name, as a prefix of auto-generated policy name. + */ + protected abstract String getDefaultPolicyName(); + + + @Override + public String getPolicyName() { + if (policyName == null) { + policyName = getDefaultPolicyName() + "-" + index.incrementAndGet(); + } + return policyName; + } + + + @Override + public void setPolicyName(String name) { + Objects.requireNonNull(name); + this.policyName = name; + } + + + @Override + public int getDuration() { + return actualDuration; + } + + + @Override + public void setDuration(int duration) { + this.duration = duration; + } + + + @Override + public int getRequests() { + return actualRequests; + } + + + @Override + public void setRequests(int requests) { + this.requests = requests; + } + + + @Override + public int increment(String identifier) { + return bucketCounter.increment(identifier); + } + + + @Override + public void destroy() { + bucketCounter.destroy(); + if (internalExecutorService != null) { + try { + internalExecutorService.shutdown(); + } catch (SecurityException e) { + // ignore + } + } + } + + + /** + * Instantiate an instance of {@link TimeBucketCounterBase} for specific time bucket size. Concrete classes + * determine its counter policy by returning different implementation instances. + * + * @param duration size of each time bucket in seconds + * @param utilityExecutor the executor + * + * @return counter instance of {@link TimeBucketCounterBase} + */ + protected abstract TimeBucketCounterBase newCounterInstance(int duration, ScheduledExecutorService utilityExecutor); + + + @Override + public void setFilterConfig(FilterConfig filterConfig) { + + ScheduledExecutorService executorService = (ScheduledExecutorService) filterConfig.getServletContext() + .getAttribute(ScheduledThreadPoolExecutor.class.getName()); + + if (executorService == null) { + internalExecutorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1); + executorService = internalExecutorService; + } + + bucketCounter = newCounterInstance(duration, executorService); + actualDuration = bucketCounter.getBucketDuration(); + actualRequests = (int) Math.round(bucketCounter.getRatio() * requests); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org