content: /**
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.aurora.common.util;
  
  import com.google.common.base.Preconditions;
  
  import org.apache.aurora.common.quantity.Amount;
  import org.apache.aurora.common.quantity.Time;
  
  /**
   * A BackoffStrategy that implements truncated binary exponential backoff.
   */
  public class TruncatedBinaryBackoff implements BackoffStrategy {
    private final long initialBackoffMs;
    private final long maxBackoffIntervalMs;
    private final boolean stopAtMax;
    private final Random random;
  
    /**
     * Creates a new TruncatedBinaryBackoff that will start by backing off for {@code initialBackoff}
     * and then backoff of twice as long each time its called until reaching the {@code maxBackoff} at
     * which point shouldContinue() will return false and any future backoffs will always wait for
     * that amount of time.
     *
     * @param initialBackoff the initial amount of time to backoff
     * @param maxBackoff the maximum amount of time to backoff
     * @param stopAtMax whether shouldContinue() returns false when the max is reached
     * @param random An instance of the random util which can be used for applying jitter to backoff.
     */
    public TruncatedBinaryBackoff(
        Amount<Long, Time> initialBackoff,
        Amount<Long, Time> maxBackoff,
        boolean stopAtMax,
        Random random) {
  
      Preconditions.checkNotNull(initialBackoff);
      Preconditions.checkNotNull(maxBackoff);
      Preconditions.checkNotNull(random);
      Preconditions.checkArgument(initialBackoff.getValue() > 0);
      Preconditions.checkArgument(maxBackoff.compareTo(initialBackoff) >= 0);
      initialBackoffMs = initialBackoff.as(Time.MILLISECONDS);
      maxBackoffIntervalMs = maxBackoff.as(Time.MILLISECONDS);
      this.stopAtMax = stopAtMax;
      this.random = random;
    }
  
    /**
     * Same as the main constructor
     * {@link TruncatedBinaryBackoff#TruncatedBinaryBackoff(Amount, Amount, boolean, Random)},
     * but this will use a default Random implementation returned by
     * {@link Random.Util#newDefaultRandom()}.
     *
     * @param initialBackoff the initial amount of time to backoff
     * @param maxBackoff the maximum amount of time to backoff
     * @param stopAtMax whether shouldContinue() returns false when the max is reached
     */
    public TruncatedBinaryBackoff(
        Amount<Long, Time> initialBackoff,
        Amount<Long, Time> maxBackoff,
        boolean stopAtMax) {
  
      this(initialBackoff, maxBackoff, stopAtMax, Random.Util.newDefaultRandom());
    }
  
    /**
     * Same as the constructor
     * {@link TruncatedBinaryBackoff#TruncatedBinaryBackoff(Amount, Amount, boolean)},
     * but this will always return true from shouldContinue().
     *
     * @param initialBackoff the initial amount of time to backoff
     * @param maxBackoff the maximum amount of time to backoff
     */
    public TruncatedBinaryBackoff(Amount<Long, Time> initialBackoff, Amount<Long, Time> maxBackoff) {
      this(initialBackoff, maxBackoff, false);
    }
  
    @Override
    public long calculateBackoffMs(long lastBackoffMs) {
      Preconditions.checkArgument(lastBackoffMs >= 0);
      long halfBackoff = (lastBackoffMs == 0) ? initialBackoffMs : lastBackoffMs;
  
      return Math.min(
          maxBackoffIntervalMs,
          halfBackoff + Math.round(random.nextDouble() * halfBackoff));
    }
  
    @Override
    public boolean shouldContinue(long lastBackoffMs) {
      Preconditions.checkArgument(lastBackoffMs >= 0);
      boolean stop = stopAtMax && (lastBackoffMs >= maxBackoffIntervalMs);
  
      return !stop;
    }
  }
  