
com.netflix.concurrency.limits.limiter.DefaultLimiter Maven / Gradle / Ivy
package com.netflix.concurrency.limits.limiter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import com.netflix.concurrency.limits.Limit;
import com.netflix.concurrency.limits.Limiter;
import com.netflix.concurrency.limits.Strategy;
import com.netflix.concurrency.limits.Strategy.Token;
import com.netflix.concurrency.limits.internal.Preconditions;
import com.netflix.concurrency.limits.limit.VegasLimit;
/**
* {@link Limiter} that combines a plugable limit algorithm and enforcement strategy to
* enforce concurrency limits to a fixed resource.
* @param
*/
public final class DefaultLimiter implements Limiter {
private final Supplier nanoClock = System::nanoTime;
private static final long DEFAULT_MIN_WINDOW_TIME = TimeUnit.SECONDS.toNanos(1);
private static final long DEFAULT_MAX_WINDOW_TIME = TimeUnit.SECONDS.toNanos(1);
/**
* Minimum observed samples to filter out sample windows with not enough significant samples
*/
private static final int DEFAULT_WINDOW_SIZE = 10;
/**
* End time for the sampling window at which point the limit should be updated
*/
private volatile long nextUpdateTime = 0;
private final Limit limit;
private final Strategy strategy;
private final long minWindowTime;
private final long maxWindowTime;
private final int windowSize;
/**
* Object tracking stats for the current sample window
*/
private final AtomicReference sample = new AtomicReference<>(new ImmutableSampleWindow());
/**
* Counter tracking the current number of inflight requests
*/
private final AtomicInteger inFlight = new AtomicInteger();
private final Object lock = new Object();
public static class Builder {
private Limit limit = VegasLimit.newDefault();
private long maxWindowTime = DEFAULT_MAX_WINDOW_TIME;
private long minWindowTime = DEFAULT_MIN_WINDOW_TIME;
private int windowSize = DEFAULT_WINDOW_SIZE;
/**
* Algorithm used to determine the new limit based on the current limit and minimum
* measured RTT in the sample window
*/
public Builder limit(Limit limit) {
Preconditions.checkArgument(limit != null, "Algorithm may not be null");
this.limit = limit;
return this;
}
/**
* Minimum window duration for sampling a new minRtt
*/
public Builder minWindowTime(long minWindowTime, TimeUnit units) {
Preconditions.checkArgument(units.toMillis(minWindowTime) >= 100, "minWindowTime must be >= 100 ms");
this.minWindowTime = units.toNanos(minWindowTime);
return this;
}
/**
* Maximum window duration for sampling a new minRtt
*/
public Builder maxWindowTime(long maxWindowTime, TimeUnit units) {
Preconditions.checkArgument(maxWindowTime >= units.toMillis(100), "minWindowTime must be >= 100 ms");
this.maxWindowTime = units.toNanos(maxWindowTime);
return this;
}
/**
* Minimum sampling window size for finding a new minimum rtt
*/
public Builder windowSize(int windowSize) {
Preconditions.checkArgument(windowSize >= 10, "Window size must be >= 10");
this.windowSize = windowSize;
return this;
}
/**
* @param strategy Strategy for enforcing the limit
*/
public DefaultLimiter build(Strategy strategy) {
Preconditions.checkArgument(strategy != null, "Strategy may not be null");
return new DefaultLimiter(this, strategy);
}
}
public static Builder newBuilder() {
return new Builder();
}
/**
* @deprecated Use {@link DefaultLimiter#newBuilder}
* @param limit
* @param strategy
*/
@Deprecated
public DefaultLimiter(Limit limit, Strategy strategy) {
Preconditions.checkArgument(limit != null, "Algorithm may not be null");
Preconditions.checkArgument(strategy != null, "Strategy may not be null");
this.limit = limit;
this.strategy = strategy;
this.windowSize = DEFAULT_WINDOW_SIZE;
this.minWindowTime = DEFAULT_MIN_WINDOW_TIME;
this.maxWindowTime = DEFAULT_MAX_WINDOW_TIME;
strategy.setLimit(limit.getLimit());
}
private DefaultLimiter(Builder builder, Strategy strategy) {
this.limit = builder.limit;
this.minWindowTime = builder.minWindowTime;
this.maxWindowTime = builder.maxWindowTime;
this.windowSize = builder.windowSize;
this.strategy = strategy;
strategy.setLimit(limit.getLimit());
}
@Override
public Optional acquire(final ContextT context) {
// Did we exceed the limit
final Token token = strategy.tryAcquire(context);
if (!token.isAcquired()) {
return Optional.empty();
}
final long startTime = nanoClock.get();
int currentMaxInFlight = inFlight.incrementAndGet();
return Optional.of(new Listener() {
@Override
public void onSuccess() {
inFlight.decrementAndGet();
token.release();
final long endTime = nanoClock.get();
final long rtt = endTime - startTime;
do {
ImmutableSampleWindow current = sample.get();
if (endTime > nextUpdateTime && isWindowReady(current)) {
synchronized (lock) {
if (sample.compareAndSet(current, new ImmutableSampleWindow())) {
current = current.addSample(rtt, currentMaxInFlight);
nextUpdateTime = endTime + Math.min(Math.max(current.getCandidateRttNanos() * 2, minWindowTime), maxWindowTime);
limit.update(current);
strategy.setLimit(limit.getLimit());
return;
}
}
} else if (sample.compareAndSet(current, current.addSample(rtt, currentMaxInFlight))) {
return;
}
// Got preempted so try again
} while(true);
}
@Override
public void onIgnore() {
inFlight.decrementAndGet();
token.release();
}
@Override
public void onDropped() {
inFlight.decrementAndGet();
token.release();
sample.getAndUpdate(current -> current.addDroppedSample(currentMaxInFlight));
}
});
}
private boolean isWindowReady(ImmutableSampleWindow sample) {
return sample.getCandidateRttNanos() < Long.MAX_VALUE && sample.getSampleCount() > windowSize;
}
protected int getLimit() {
return limit.getLimit();
}
@Override
public String toString() {
return "DefaultLimiter [RTT_candidate=" + TimeUnit.NANOSECONDS.toMicros(sample.get().getCandidateRttNanos()) / 1000.0
+ ", maxInFlight=" + inFlight
+ ", " + limit
+ ", " + strategy
+ "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy