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