Skip to content

Commit

Permalink
revoke API change
Browse files Browse the repository at this point in the history
RateLimiter: keep api as stable as possible.
  • Loading branch information
Chenjp committed Dec 17, 2024
1 parent 6ddbed6 commit 6571f66
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 35 deletions.
26 changes: 4 additions & 22 deletions java/org/apache/catalina/filters/RateLimitFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.apache.catalina.filters;

import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
Expand All @@ -30,7 +29,6 @@
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>
Expand Down Expand Up @@ -202,30 +200,23 @@ protected boolean isConfigProblemFatal() {
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);

ScheduledExecutorService utilityExecutor = (ScheduledExecutorService) filterConfig.getServletContext()
.getAttribute(ScheduledThreadPoolExecutor.class.getName());
if (utilityExecutor == null) {
if (newExecutorService == null) {
newExecutorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1);
}
utilityExecutor = newExecutorService;
}

try {
rateLimiter = (RateLimiter) Class.forName(rateLimitClassName).getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new ServletException(e);
}

rateLimiter.setDuration(bucketDuration);
rateLimiter.setRequests(bucketRequests);
rateLimiter.setFilterConfig(filterConfig);

if (policyName != null) {
String trimmedName = policyName.trim();
if (!trimmedName.isEmpty()) {
rateLimiter.setPolicyName(trimmedName);
}
}

rateLimiter.initialize(utilityExecutor, bucketDuration, bucketRequests);

filterName = filterConfig.getFilterName();

log.info(sm.getString("rateLimitFilter.initialized", filterName, Integer.valueOf(bucketRequests),
Expand Down Expand Up @@ -262,18 +253,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
chain.doFilter(request, response);
}

private ScheduledExecutorService newExecutorService = null;

@Override
public void destroy() {
rateLimiter.destroy();
if (newExecutorService != null) {
try {
newExecutorService.shutdown();
} catch (SecurityException e) {
// ignore
}
}
super.destroy();
}

Expand Down
5 changes: 5 additions & 0 deletions java/org/apache/catalina/util/FastRateLimiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ protected String getDefaultPolicyName() {
protected TimeBucketCounterBase newCounterInstance(ScheduledExecutorService executorService, int duration) {
return new TimeBucketCounter(executorService, duration);
}

@Override
public TimeBucketCounter getBucketCounter() {
return (TimeBucketCounter)bucketCounter;
}
}
26 changes: 19 additions & 7 deletions java/org/apache/catalina/util/RateLimiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

package org.apache.catalina.util;

import java.util.concurrent.ScheduledExecutorService;
import jakarta.servlet.FilterConfig;

public interface RateLimiter {

Expand All @@ -26,11 +26,25 @@ public interface RateLimiter {
*/
int getDuration();

/**
* Sets the configured duration value in seconds.
*
* @param duration The duration of the time window in seconds
*/
void setDuration(int duration);

/**
* @return the maximum number of requests allowed per time window
*/
int getRequests();

/**
* Sets the configured number of requests allowed per time window.
*
* @param requests The number of requests per time window
*/
void setRequests(int requests);

/**
* Increments the number of requests by the given identifier in the current time window.
*
Expand All @@ -46,13 +60,11 @@ public interface RateLimiter {
void destroy();

/**
* Initialize with parameters, start {@link TimeBucketCounterBase}.
* Pass the FilterConfig to configure the filter.
*
* @param executorService the executor
* @param duration the duration of the time window in seconds
* @param requests the configured number of requests allowed per time window
* @param filterConfig The FilterConfig used to configure the associated filter
*/
void initialize(ScheduledExecutorService executorService, int duration, int requests);
void setFilterConfig(FilterConfig filterConfig);

/**
* @return name of RateLimit policy
Expand Down Expand Up @@ -80,7 +92,7 @@ default String getPolicy() {
/**
* Provide the quota header for this rate limit for a given request count within the current time window.
*
* @param requestCount The request count within the current time window
* @param requestCount The request count within the current time window
*
* @return the quota header for the given value of request count
*
Expand Down
43 changes: 37 additions & 6 deletions java/org/apache/catalina/util/RateLimiterBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;

import jakarta.servlet.FilterConfig;

/**
* Basic implementation of {@link RateLimiter}, provides runtime data maintenance mechanism monitor.
*/
Expand All @@ -29,8 +32,10 @@ public abstract class RateLimiterBase implements RateLimiter {

TimeBucketCounterBase bucketCounter;

int requests;
int actualRequests;

int duration;
int actualDuration;

// Initial policy name can be rewritten by setPolicyName()
Expand Down Expand Up @@ -63,11 +68,21 @@ 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);
Expand All @@ -76,6 +91,13 @@ public int increment(String identifier) {
@Override
public void destroy() {
bucketCounter.destroy();
if (newExecutorService != null) {
try {
newExecutorService.shutdown();
} catch (SecurityException e) {
// ignore
}
}
}

/**
Expand All @@ -90,17 +112,20 @@ public void destroy() {
protected abstract TimeBucketCounterBase newCounterInstance(ScheduledExecutorService utilityExecutor, int duration);

@Override
public void initialize(ScheduledExecutorService utilityExecutor, int duration, int requests) {
if (bucketCounter != null) {
bucketCounter.destroy();
}
public void setFilterConfig(FilterConfig filterConfig) {

ScheduledExecutorService executorService = (ScheduledExecutorService) filterConfig.getServletContext()
.getAttribute(ScheduledThreadPoolExecutor.class.getName());

bucketCounter = newCounterInstance(utilityExecutor, duration);
if (executorService == null) {
newExecutorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1);
executorService = newExecutorService;
}

bucketCounter = newCounterInstance(executorService, duration);
actualDuration = bucketCounter.getBucketDuration();
actualRequests = (int) Math.round(bucketCounter.getRatio() * requests);
}

/**
* Returns the internal instance of {@link TimeBucketCounterBase}
*
Expand All @@ -109,4 +134,10 @@ public void initialize(ScheduledExecutorService utilityExecutor, int duration, i
public TimeBucketCounterBase getBucketCounter() {
return bucketCounter;
}

/**
* The self-owned utility executor, will be instantiated only when ScheduledThreadPoolExecutor is absent during
* filter configure phase.
*/
private ScheduledThreadPoolExecutor newExecutorService = null;
}

0 comments on commit 6571f66

Please sign in to comment.