001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.util;
019
020import java.util.concurrent.ThreadLocalRandom;
021import java.util.concurrent.TimeUnit;
022import org.apache.yetus.audience.InterfaceAudience;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
027
028/**
029 * Operation retry accounting. Use to calculate wait period,
030 * {@link #getBackoffTimeAndIncrementAttempts()}}, or for performing wait,
031 * {@link #sleepUntilNextRetry()}, in accordance with a {@link RetryConfig}, initial settings, and a
032 * Retry Policy, (See org.apache.hadoop.io.retry.RetryPolicy). Like <a
033 * href=https://github.com/rholder/guava-retrying>guava-retrying</a>.
034 * @since 0.92.0
035 * @see RetryCounterFactory
036 */
037@InterfaceAudience.Private
038public class RetryCounter {
039  /**
040   * Configuration for a retry counter
041   */
042  public static class RetryConfig {
043    private int maxAttempts;
044    private long sleepInterval;
045    private long maxSleepTime;
046    private TimeUnit timeUnit;
047    private BackoffPolicy backoffPolicy;
048    private float jitter;
049
050    private static final BackoffPolicy DEFAULT_BACKOFF_POLICY = new ExponentialBackoffPolicy();
051
052    public RetryConfig() {
053      maxAttempts = 1;
054      sleepInterval = 1000;
055      maxSleepTime = -1;
056      timeUnit = TimeUnit.MILLISECONDS;
057      backoffPolicy = DEFAULT_BACKOFF_POLICY;
058      jitter = 0.0f;
059    }
060
061    public RetryConfig(int maxAttempts, long sleepInterval, long maxSleepTime, TimeUnit timeUnit,
062      BackoffPolicy backoffPolicy) {
063      this.maxAttempts = maxAttempts;
064      this.sleepInterval = sleepInterval;
065      this.maxSleepTime = maxSleepTime;
066      this.timeUnit = timeUnit;
067      this.backoffPolicy = backoffPolicy;
068    }
069
070    public RetryConfig setBackoffPolicy(BackoffPolicy backoffPolicy) {
071      this.backoffPolicy = backoffPolicy;
072      return this;
073    }
074
075    public RetryConfig setMaxAttempts(int maxAttempts) {
076      this.maxAttempts = maxAttempts;
077      return this;
078    }
079
080    public RetryConfig setMaxSleepTime(long maxSleepTime) {
081      this.maxSleepTime = maxSleepTime;
082      return this;
083    }
084
085    public RetryConfig setSleepInterval(long sleepInterval) {
086      this.sleepInterval = sleepInterval;
087      return this;
088    }
089
090    public RetryConfig setTimeUnit(TimeUnit timeUnit) {
091      this.timeUnit = timeUnit;
092      return this;
093    }
094
095    public RetryConfig setJitter(float jitter) {
096      Preconditions.checkArgument(jitter >= 0.0f && jitter < 1.0f,
097        "Invalid jitter: %s, should be in range [0.0, 1.0)", jitter);
098      this.jitter = jitter;
099      return this;
100    }
101
102    public int getMaxAttempts() {
103      return maxAttempts;
104    }
105
106    public long getMaxSleepTime() {
107      return maxSleepTime;
108    }
109
110    public long getSleepInterval() {
111      return sleepInterval;
112    }
113
114    public TimeUnit getTimeUnit() {
115      return timeUnit;
116    }
117
118    public float getJitter() {
119      return jitter;
120    }
121
122    public BackoffPolicy getBackoffPolicy() {
123      return backoffPolicy;
124    }
125  }
126
127  private static long addJitter(long interval, float jitter) {
128    long jitterInterval = (long) (interval * ThreadLocalRandom.current().nextFloat() * jitter);
129    return interval + jitterInterval;
130  }
131
132  /**
133   * Policy for calculating sleeping intervals between retry attempts
134   */
135  public static class BackoffPolicy {
136    public long getBackoffTime(RetryConfig config, int attempts) {
137      return addJitter(config.getSleepInterval(), config.getJitter());
138    }
139  }
140
141  public static class ExponentialBackoffPolicy extends BackoffPolicy {
142    @Override
143    public long getBackoffTime(RetryConfig config, int attempts) {
144      long backoffTime = (long) (config.getSleepInterval() * Math.pow(2, attempts));
145      return addJitter(backoffTime, config.getJitter());
146    }
147  }
148
149  public static class ExponentialBackoffPolicyWithLimit extends ExponentialBackoffPolicy {
150    @Override
151    public long getBackoffTime(RetryConfig config, int attempts) {
152      long backoffTime = super.getBackoffTime(config, attempts);
153      return config.getMaxSleepTime() > 0
154        ? Math.min(backoffTime, config.getMaxSleepTime())
155        : backoffTime;
156    }
157  }
158
159  private static final Logger LOG = LoggerFactory.getLogger(RetryCounter.class);
160
161  private RetryConfig retryConfig;
162  private int attempts;
163
164  public RetryCounter(int maxAttempts, long sleepInterval, TimeUnit timeUnit) {
165    this(new RetryConfig(maxAttempts, sleepInterval, -1, timeUnit, new ExponentialBackoffPolicy()));
166  }
167
168  public RetryCounter(RetryConfig retryConfig) {
169    this.attempts = 0;
170    this.retryConfig = retryConfig;
171  }
172
173  public int getMaxAttempts() {
174    return retryConfig.getMaxAttempts();
175  }
176
177  /**
178   * Sleep for a back off time as supplied by the backoff policy, and increases the attempts
179   */
180  public void sleepUntilNextRetry() throws InterruptedException {
181    int attempts = getAttemptTimes();
182    long sleepTime = getBackoffTime();
183    LOG.trace("Sleeping {} ms before retry #{}...", sleepTime, attempts);
184    retryConfig.getTimeUnit().sleep(sleepTime);
185    useRetry();
186  }
187
188  public boolean shouldRetry() {
189    return attempts < retryConfig.getMaxAttempts();
190  }
191
192  public void useRetry() {
193    attempts++;
194  }
195
196  public boolean isRetry() {
197    return attempts > 0;
198  }
199
200  public int getAttemptTimes() {
201    return attempts;
202  }
203
204  public long getBackoffTime() {
205    return this.retryConfig.backoffPolicy.getBackoffTime(this.retryConfig, getAttemptTimes());
206  }
207
208  public long getBackoffTimeAndIncrementAttempts() {
209    long backoffTime = getBackoffTime();
210    useRetry();
211    return backoffTime;
212  }
213}