All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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