11package com .netflix .concurrency .limits .limiter ;
22
3- import java .util .Optional ;
4- import java .util .concurrent .TimeUnit ;
5- import java .util .concurrent .atomic .AtomicInteger ;
6- import java .util .concurrent .atomic .AtomicLong ;
7- import java .util .concurrent .atomic .AtomicReference ;
8- import java .util .function .Supplier ;
9-
103import com .netflix .concurrency .limits .Limit ;
114import com .netflix .concurrency .limits .Limiter ;
125import com .netflix .concurrency .limits .Strategy ;
136import com .netflix .concurrency .limits .Strategy .Token ;
147import com .netflix .concurrency .limits .internal .Preconditions ;
158import com .netflix .concurrency .limits .limit .VegasLimit ;
169
10+ import java .util .Optional ;
11+ import java .util .concurrent .TimeUnit ;
12+ import java .util .concurrent .atomic .AtomicInteger ;
13+ import java .util .concurrent .atomic .AtomicReference ;
14+ import java .util .function .Supplier ;
15+
1716/**
1817 * {@link Limiter} that combines a plugable limit algorithm and enforcement strategy to
1918 * enforce concurrency limits to a fixed resource.
@@ -23,42 +22,31 @@ public final class DefaultLimiter<ContextT> implements Limiter<ContextT> {
2322 private final Supplier <Long > nanoClock = System ::nanoTime ;
2423
2524 private static final long DEFAULT_MIN_WINDOW_TIME = TimeUnit .SECONDS .toNanos (1 );
26- private static final int DEFAULT_WINDOW_SIZE = 10 ;
27-
25+ private static final long DEFAULT_MAX_WINDOW_TIME = TimeUnit . SECONDS . toNanos ( 1 ) ;
26+
2827 /**
2928 * Minimum observed samples to filter out sample windows with not enough significant samples
3029 */
31- private static final int MIN_WINDOW_SAMPLE_COUNT = 10 ;
30+ private static final int DEFAULT_WINDOW_SIZE = 10 ;
3231
3332 /**
3433 * Minimum observed max inflight to filter out sample windows with not enough significant data
3534 */
36- private static final int MIN_WINDOW_MAX_INFLIGHT = 1 ;
35+ private static final int MIN_WINDOW_MAX_INFLIGHT = 2 ;
3736
3837 /**
3938 * End time for the sampling window at which point the limit should be updated
4039 */
41- private final AtomicLong nextUpdateTime = new AtomicLong () ;
40+ private volatile long nextUpdateTime = 0 ;
4241
43- /**
44- * Algorithm used to determine the new limit based on the current limit and minimum
45- * measured RTT in the sample window
46- */
4742 private final Limit limit ;
4843
49- /**
50- * Strategy for enforcing the limit
51- */
5244 private final Strategy <ContextT > strategy ;
5345
54- /**
55- * Minimum window size in nanonseconds for sampling a new minRtt
56- */
5746 private final long minWindowTime ;
5847
59- /**
60- * Sampling window size in multiple of the measured minRtt
61- */
48+ private final long maxWindowTime ;
49+
6250 private final int windowSize ;
6351
6452 /**
@@ -73,27 +61,50 @@ public final class DefaultLimiter<ContextT> implements Limiter<ContextT> {
7361
7462 public static class Builder {
7563 private Limit limit = VegasLimit .newDefault ();
64+ private long maxWindowTime = DEFAULT_MAX_WINDOW_TIME ;
7665 private long minWindowTime = DEFAULT_MIN_WINDOW_TIME ;
7766 private int windowSize = DEFAULT_WINDOW_SIZE ;
7867
68+ /**
69+ * Algorithm used to determine the new limit based on the current limit and minimum
70+ * measured RTT in the sample window
71+ */
7972 public Builder limit (Limit limit ) {
8073 Preconditions .checkArgument (limit != null , "Algorithm may not be null" );
8174 this .limit = limit ;
8275 return this ;
8376 }
8477
78+ /**
79+ * Minimum window duration for sampling a new minRtt
80+ */
8581 public Builder minWindowTime (long minWindowTime , TimeUnit units ) {
8682 Preconditions .checkArgument (units .toMillis (minWindowTime ) >= 100 , "minWindowTime must be >= 100 ms" );
8783 this .minWindowTime = units .toNanos (minWindowTime );
8884 return this ;
8985 }
9086
87+ /**
88+ * Maximum window duration for sampling a new minRtt
89+ */
90+ public Builder maxWindowTime (long maxWindowTime , TimeUnit units ) {
91+ Preconditions .checkArgument (maxWindowTime >= units .toMillis (100 ), "minWindowTime must be >= 100 ms" );
92+ this .maxWindowTime = units .toNanos (maxWindowTime );
93+ return this ;
94+ }
95+
96+ /**
97+ * Minimum sampling window size for finding a new minimum rtt
98+ */
9199 public Builder windowSize (int windowSize ) {
92100 Preconditions .checkArgument (windowSize >= 10 , "Window size must be >= 10" );
93101 this .windowSize = windowSize ;
94102 return this ;
95103 }
96104
105+ /**
106+ * @param strategy Strategy for enforcing the limit
107+ */
97108 public <ContextT > DefaultLimiter <ContextT > build (Strategy <ContextT > strategy ) {
98109 Preconditions .checkArgument (strategy != null , "Strategy may not be null" );
99110 return new DefaultLimiter <ContextT >(this , strategy );
@@ -117,12 +128,14 @@ public DefaultLimiter(Limit limit, Strategy<ContextT> strategy) {
117128 this .strategy = strategy ;
118129 this .windowSize = DEFAULT_WINDOW_SIZE ;
119130 this .minWindowTime = DEFAULT_MIN_WINDOW_TIME ;
131+ this .maxWindowTime = DEFAULT_MAX_WINDOW_TIME ;
120132 strategy .setLimit (limit .getLimit ());
121133 }
122134
123135 private DefaultLimiter (Builder builder , Strategy <ContextT > strategy ) {
124136 this .limit = builder .limit ;
125137 this .minWindowTime = builder .minWindowTime ;
138+ this .maxWindowTime = builder .maxWindowTime ;
126139 this .windowSize = builder .windowSize ;
127140 this .strategy = strategy ;
128141 strategy .setLimit (limit .getLimit ());
@@ -150,16 +163,17 @@ public void onSuccess() {
150163
151164 sample .getAndUpdate (current -> current .addSample (rtt , currentMaxInFlight ));
152165
153- long updateTime = nextUpdateTime .get ();
154- if (endTime >= updateTime ) {
155- long nextUpdate = endTime + Math .max (minWindowTime , rtt * windowSize );
156- if (nextUpdateTime .compareAndSet (updateTime , nextUpdate )) {
157- ImmutableSample last = sample .getAndUpdate (ImmutableSample ::reset );
158- if (last .getCandidateRttNanos () < Integer .MAX_VALUE
159- && last .getSampleCount () > MIN_WINDOW_SAMPLE_COUNT
160- && last .getMaxInFlight () > MIN_WINDOW_MAX_INFLIGHT ) {
161- limit .update (last );
162- strategy .setLimit (limit .getLimit ());
166+ if (endTime >= nextUpdateTime ) {
167+ synchronized (this ) {
168+ // Double check inside the lock
169+ if (endTime >= nextUpdateTime ) {
170+ ImmutableSample last = sample .get ();
171+ if (isSampleReady (last )) {
172+ nextUpdateTime = endTime + Math .min (Math .max (last .getCandidateRttNanos () * 2 , minWindowTime ), maxWindowTime );
173+ sample .set (new ImmutableSample ());
174+ limit .update (last );
175+ strategy .setLimit (limit .getLimit ());
176+ }
163177 }
164178 }
165179 }
@@ -180,6 +194,12 @@ public void onDropped() {
180194 });
181195 }
182196
197+ private boolean isSampleReady (ImmutableSample sample ) {
198+ return sample .getCandidateRttNanos () < Long .MAX_VALUE
199+ && sample .getSampleCount () > windowSize
200+ && sample .getMaxInFlight () > MIN_WINDOW_MAX_INFLIGHT ;
201+ }
202+
183203 protected int getLimit () {
184204 return limit .getLimit ();
185205 }
0 commit comments