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

org.apache.ratis.retry.MultipleLinearRandomRetry Maven / Gradle / Ivy

There is a newer version: 3.1.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.ratis.retry;

import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.TimeDuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Given pairs of number of retries and sleep time (n0, t0), (n1, t1), ...,
 * the first n0 retries sleep t0 milliseconds on average,
 * the following n1 retries sleep t1 milliseconds on average, and so on.
 *
 * For all the sleep, the actual sleep time is randomly uniform distributed
 * in the close interval [0.5t, 1.5t], where t is the sleep time specified.
 *
 * The objects of this class are immutable.
 */
public final class MultipleLinearRandomRetry implements RetryPolicy {
  static final Logger LOG = LoggerFactory.getLogger(MultipleLinearRandomRetry.class);

  /** Pairs of numRetries and sleepSeconds */
  private static class Pair {
    private final int numRetries;
    private final TimeDuration sleepTime;

    Pair(int numRetries, TimeDuration sleepTime) {
      if (numRetries < 0) {
        throw new IllegalArgumentException("numRetries = " + numRetries+" < 0");
      }
      if (sleepTime.isNegative()) {
        throw new IllegalArgumentException("sleepTime = " + sleepTime + " < 0");
      }

      this.numRetries = numRetries;
      this.sleepTime = sleepTime;
    }

    TimeDuration getRandomSleepTime() {
      // 0.5 <= ratio < 1.5
      final double ratio = ThreadLocalRandom.current().nextDouble() + 0.5;
      return sleepTime.multiply(ratio);
    }

    @Override
    public String toString() {
      return numRetries + "x" + sleepTime;
    }
  }

  private final List pairs;
  private final Supplier myString;

  private MultipleLinearRandomRetry(List pairs) {
    if (pairs == null || pairs.isEmpty()) {
      throw new IllegalArgumentException("pairs must be neither null nor empty.");
    }
    this.pairs = Collections.unmodifiableList(pairs);
    this.myString = JavaUtils.memoize(() -> JavaUtils.getClassSimpleName(getClass()) + pairs);
  }

  @Override
  public Action handleAttemptFailure(Event event) {
    final Pair p = searchPair(event.getAttemptCount());
    return p == null? NO_RETRY_ACTION: p::getRandomSleepTime;
  }

  /**
   * Given the current number of retry, search the corresponding pair.
   * @return the corresponding pair,
   *   or null if the current number of retry > maximum number of retry.
   */
  private Pair searchPair(int curRetry) {
    int i = 0;
    for(; i < pairs.size() && curRetry > pairs.get(i).numRetries; i++) {
      curRetry -= pairs.get(i).numRetries;
    }
    return i == pairs.size()? null: pairs.get(i);
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public boolean equals(final Object that) {
    if (this == that) {
      return true;
    } else if (that == null || this.getClass() != that.getClass()) {
      return false;
    }
    return this.toString().equals(that.toString());
  }

  @Override
  public String toString() {
    return myString.get();
  }

  /**
   * Parse the given string as a MultipleLinearRandomRetry object.
   * The format of the string is "t_1, n_1, t_2, n_2, ...",
   * where t_i and n_i are the i-th pair of sleep time and number of retries.
   * Note that the white spaces in the string are ignored.
   *
   * @return the parsed object, or null if the parsing fails.
   */
  public static MultipleLinearRandomRetry parseCommaSeparated(String input) {
    final String[] elements = input.split(",");
    if (elements.length == 0) {
      LOG.warn("Illegal value: there is no element in \"{}\".", input);
      return null;
    }
    if (elements.length % 2 != 0) {
      LOG.warn("Illegal value: the number of elements in \"{}\" is {} but an even number of elements is expected.",
          input, elements.length);
      return null;
    }

    final List pairs = new ArrayList<>();
    for(int i = 0; i < elements.length; ) {
      //parse the i-th sleep-time
      final TimeDuration sleep = parseElement(elements, i++, input, MultipleLinearRandomRetry::parsePositiveTime);
      if (sleep == null) {
        return null; //parse fails
      }
      //parse the i-th number-of-retries
      final Integer retries = parseElement(elements, i++, input, MultipleLinearRandomRetry::parsePositiveInt);
      if (retries == null) {
        return null; //parse fails
      }

      pairs.add(new Pair(retries, sleep));
    }
    return new MultipleLinearRandomRetry(pairs);
  }

  private static TimeDuration parsePositiveTime(String s) {
    final TimeDuration t = TimeDuration.valueOf(s, TimeUnit.MILLISECONDS);
    if (t.isNonPositive()) {
      throw new IllegalArgumentException("Non-positive value: " + t);
    }
    return t;
  }

  private static int parsePositiveInt(String trimmed) {
    final int n = Integer.parseInt(trimmed);
    if (n <= 0) {
      throw new IllegalArgumentException("Non-positive value: " + n);
    }
    return n;
  }

  private static  E parseElement(String[] elements, int i, String input, Function parser) {
    final String s = elements[i].trim().replace("_", "");
    try {
      return parser.apply(s);
    } catch(Exception t) {
      LOG.warn("Failed to parse \"{}\", which is the index {} element in \"{}\"", s, i, input, t);
      return null;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy