5252 */
5353public class ThresholdBundler <E > {
5454
55- private ImmutableList <BundlingThreshold <E >> thresholds ;
56- private ImmutableList <ExternalThreshold <E >> externalThresholds ;
55+ private ImmutableList <BundlingThreshold <E >> thresholdPrototypes ;
56+ private ImmutableList <ExternalThreshold <E >> externalThresholdPrototypes ;
5757 private final Duration maxDelay ;
5858
5959 private final Lock lock = new ReentrantLock ();
6060 private final Condition bundleCondition = lock .newCondition ();
61- private boolean bundleReady = false ;
62- private BundleHandle currentBundleHandle ;
63-
64- private Stopwatch bundleStopwatch ;
65- private final List <E > data = new ArrayList <>();
61+ private Bundle currentOpenBundle ;
62+ private List <Bundle > closedBundles = new ArrayList <>();
6663
6764 private ThresholdBundler (ImmutableList <BundlingThreshold <E >> thresholds ,
6865 ImmutableList <ExternalThreshold <E >> externalThresholds ,
6966 Duration maxDelay ) {
70- this .thresholds = copyResetThresholds (Preconditions .checkNotNull (thresholds ));
71- this .externalThresholds = copyResetExternalThresholds (
67+ this .thresholdPrototypes = copyResetThresholds (Preconditions .checkNotNull (thresholds ));
68+ this .externalThresholdPrototypes = copyResetExternalThresholds (
7269 Preconditions .checkNotNull (externalThresholds ));
7370 this .maxDelay = maxDelay ;
74- this .currentBundleHandle = new BundleHandle ( externalThresholds ) ;
71+ this .currentOpenBundle = null ;
7572 }
7673
7774 /**
@@ -154,32 +151,37 @@ public ThresholdBundleHandle add(E e) {
154151 final Lock lock = this .lock ;
155152 lock .lock ();
156153 try {
157- boolean signal = false ;
158- // TODO verify invariant: bundleStopwatch == null iff size() == 0
159- if (data .size () == 0 ) {
160- bundleStopwatch = Stopwatch .createStarted ();
161- // we want to trigger the signal so that we switch the await from an unbounded
162- // await to a time-bounded await.
163- signal = true ;
164- for (ExternalThreshold <E > threshold : externalThresholds ) {
165- threshold .startBundle ();
166- }
154+ validateLimits (e );
155+
156+ boolean signalBundleIsReady = false ;
157+ Bundle bundleOfAddedItem = null ;
158+ if (currentOpenBundle == null ) {
159+ currentOpenBundle = new Bundle (thresholdPrototypes , externalThresholdPrototypes , maxDelay );
160+ currentOpenBundle .start ();
161+ signalBundleIsReady = true ;
167162 }
168- data .add (e );
169- if (!bundleReady ) {
170- for (BundlingThreshold <E > threshold : thresholds ) {
171- threshold .accumulate (e );
172- if (threshold .isThresholdReached ()) {
173- bundleReady = true ;
174- signal = true ;
175- break ;
176- }
163+
164+ if (currentOpenBundle .canAccept (e )) {
165+ currentOpenBundle .add (e );
166+ bundleOfAddedItem = currentOpenBundle ;
167+ if (currentOpenBundle .isAnyThresholdReached ()) {
168+ signalBundleIsReady = true ;
169+ closedBundles .add (currentOpenBundle );
170+ currentOpenBundle = null ;
177171 }
172+ } else {
173+ signalBundleIsReady = true ;
174+ closedBundles .add (currentOpenBundle );
175+ currentOpenBundle = new Bundle (thresholdPrototypes , externalThresholdPrototypes , maxDelay );
176+ currentOpenBundle .start ();
177+ currentOpenBundle .add (e );
178+ bundleOfAddedItem = currentOpenBundle ;
178179 }
179- if (signal ) {
180+
181+ if (signalBundleIsReady ) {
180182 bundleCondition .signalAll ();
181183 }
182- return currentBundleHandle ;
184+ return bundleOfAddedItem ;
183185 } finally {
184186 lock .unlock ();
185187 }
@@ -193,7 +195,10 @@ public void flush() {
193195 final Lock lock = this .lock ;
194196 lock .lock ();
195197 try {
196- bundleReady = true ;
198+ if (currentOpenBundle != null ) {
199+ closedBundles .add (currentOpenBundle );
200+ currentOpenBundle = null ;
201+ }
197202 bundleCondition .signalAll ();
198203 } finally {
199204 lock .unlock ();
@@ -206,23 +211,24 @@ public void flush() {
206211 *
207212 * @return the number of items added to 'bundle'.
208213 */
209- public int drainTo (Collection <? super E > bundle ) {
214+ public int drainNextBundleTo (Collection <? super E > outputCollection ) {
210215 final Lock lock = this .lock ;
211216 lock .lock ();
212217 try {
213- int dataSize = data .size ();
214-
215- bundle .addAll (data );
216- data .clear ();
217- currentBundleHandle = new BundleHandle (externalThresholds );
218-
219- thresholds = copyResetThresholds (thresholds );
220- externalThresholds = copyResetExternalThresholds (externalThresholds );
221-
222- bundleStopwatch = null ;
223- bundleReady = false ;
218+ Bundle outBundle = null ;
219+ if (closedBundles .size () > 0 ) {
220+ outBundle = closedBundles .remove (0 );
221+ } else if (currentOpenBundle != null ) {
222+ outBundle = currentOpenBundle ;
223+ currentOpenBundle = null ;
224+ }
224225
225- return dataSize ;
226+ if (outBundle != null ) {
227+ outputCollection .addAll (outBundle .getData ());
228+ return outputCollection .size ();
229+ } else {
230+ return 0 ;
231+ }
226232 } finally {
227233 lock .unlock ();
228234 }
@@ -236,66 +242,61 @@ public List<E> takeBundle() throws InterruptedException {
236242 lock .lockInterruptibly ();
237243 try {
238244 while (shouldWait ()) {
239- if (data . size () == 0 || maxDelay == null ) {
245+ if (currentOpenBundle == null || maxDelay == null ) {
240246 // if an element gets added, this will be signaled, then we will re-check the while-loop
241247 // condition to see if the delay or other thresholds have been exceeded,
242248 // and if none of these are true, then we will arrive at the time-bounded
243249 // await in the else clause.
244250 bundleCondition .await ();
245251 } else {
246- bundleCondition .await (getDelayLeft ().getMillis (), TimeUnit .MILLISECONDS );
252+ bundleCondition .await (currentOpenBundle .getDelayLeft ().getMillis (),
253+ TimeUnit .MILLISECONDS );
247254 }
248255 }
256+
249257 List <E > bundle = new ArrayList <>();
250- drainTo (bundle );
258+ drainNextBundleTo (bundle );
251259 return bundle ;
252260 } finally {
253261 lock .unlock ();
254262 }
255263 }
256264
257- /**
258- * Returns the number of elements queued up in the bundler.
259- */
260- public int size () {
265+ public boolean isEmpty () {
261266 final Lock lock = this .lock ;
262267 lock .lock ();
263268 try {
264- return data .size ();
269+ if (closedBundles .size () > 0 ) {
270+ return false ;
271+ }
272+ if (currentOpenBundle != null ) {
273+ return false ;
274+ }
275+ return true ;
265276 } finally {
266277 lock .unlock ();
267278 }
268279 }
269280
270- /**
271- * Returns the elements queued up in the bundler.
272- */
273- public Object [] toArray () {
274- final Lock lock = this .lock ;
275- lock .lock ();
276- try {
277- return data .toArray ();
278- } finally {
279- lock .unlock ();
281+ private void validateLimits (E e ) {
282+ for (BundlingThreshold <E > threshold : thresholdPrototypes ) {
283+ if (!threshold .canAccept (e )) {
284+ throw new IllegalArgumentException ("Single item too large for bundle" );
285+ }
280286 }
281287 }
282288
283289 private boolean shouldWait () {
284- if (data .size () == 0 ) {
285- return true ;
286- }
287- if (bundleReady ) {
290+ if (closedBundles .size () > 0 ) {
288291 return false ;
289292 }
293+ if (currentOpenBundle == null ) {
294+ return true ;
295+ }
290296 if (maxDelay == null ) {
291297 return true ;
292298 }
293- return getDelayLeft ().getMillis () > 0 ;
294- }
295-
296- // pre-condition: data.size() > 0 ( === bundleStopwatch != null)
297- private Duration getDelayLeft () {
298- return Duration .millis (maxDelay .getMillis () - bundleStopwatch .elapsed (TimeUnit .MILLISECONDS ));
299+ return currentOpenBundle .getDelayLeft ().getMillis () > 0 ;
299300 }
300301
301302 private static <E > ImmutableList <BundlingThreshold <E >> copyResetThresholds (
@@ -324,11 +325,59 @@ private static <E> ImmutableList<ExternalThreshold<E>> copyResetExternalThreshol
324325 * a ThresholdBundler, but only if the bundle referenced is still the active
325326 * one.
326327 */
327- private class BundleHandle implements ThresholdBundleHandle {
328+ private class Bundle implements ThresholdBundleHandle {
329+ private final ImmutableList <BundlingThreshold <E >> thresholds ;
328330 private final ImmutableList <ExternalThreshold <E >> externalThresholds ;
331+ private final Duration maxDelay ;
332+ private final List <E > data = new ArrayList <>();
333+ private Stopwatch stopwatch ;
334+
335+ private Bundle (ImmutableList <BundlingThreshold <E >> thresholds ,
336+ ImmutableList <ExternalThreshold <E >> externalThresholds ,
337+ Duration maxDelay ) {
338+ this .thresholds = copyResetThresholds (thresholds );
339+ this .externalThresholds = copyResetExternalThresholds (externalThresholds );
340+ this .maxDelay = maxDelay ;
341+ }
329342
330- private BundleHandle (ImmutableList <ExternalThreshold <E >> externalThresholds ) {
331- this .externalThresholds = externalThresholds ;
343+ private void start () {
344+ stopwatch = Stopwatch .createStarted ();
345+ for (ExternalThreshold <E > threshold : externalThresholds ) {
346+ threshold .startBundle ();
347+ }
348+ }
349+
350+ private boolean canAccept (E e ) {
351+ for (BundlingThreshold <E > threshold : thresholds ) {
352+ if (!threshold .canAccept (e )) {
353+ return false ;
354+ }
355+ }
356+ return true ;
357+ }
358+
359+ private void add (E e ) {
360+ data .add (e );
361+ for (BundlingThreshold <E > threshold : thresholds ) {
362+ threshold .accumulate (e );
363+ }
364+ }
365+
366+ private List <E > getData () {
367+ return data ;
368+ }
369+
370+ private Duration getDelayLeft () {
371+ return Duration .millis (maxDelay .getMillis () - stopwatch .elapsed (TimeUnit .MILLISECONDS ));
372+ }
373+
374+ private boolean isAnyThresholdReached () {
375+ for (BundlingThreshold <E > threshold : thresholds ) {
376+ if (threshold .isThresholdReached ()) {
377+ return true ;
378+ }
379+ }
380+ return false ;
332381 }
333382
334383 @ Override
@@ -351,7 +400,7 @@ public void flush() {
351400 lock .lock ();
352401
353402 try {
354- if (ThresholdBundler .this .currentBundleHandle != this ) {
403+ if (ThresholdBundler .this .currentOpenBundle != this ) {
355404 return ;
356405 }
357406 ThresholdBundler .this .flush ();
0 commit comments