1 /**
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.apache.hadoop.hbase.util;
20 
21 import java.util.concurrent.TimeUnit;
22 
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.apache.hadoop.hbase.classification.InterfaceAudience;
26 
27 @InterfaceAudience.Private
28 public class RetryCounter {
29 
30   /**
31    *  Configuration for a retry counter
32    */
33   public static class RetryConfig {
34     private int maxAttempts;
35     private long sleepInterval;
36     private long maxSleepTime;
37     private TimeUnit timeUnit;
38     private BackoffPolicy backoffPolicy;
39 
40     private static final BackoffPolicy DEFAULT_BACKOFF_POLICY = new ExponentialBackoffPolicy();
41 
RetryConfig()42     public RetryConfig() {
43       maxAttempts    = 1;
44       sleepInterval = 1000;
45       maxSleepTime  = -1;
46       timeUnit = TimeUnit.MILLISECONDS;
47       backoffPolicy = DEFAULT_BACKOFF_POLICY;
48     }
49 
RetryConfig(int maxAttempts, long sleepInterval, long maxSleepTime, TimeUnit timeUnit, BackoffPolicy backoffPolicy)50     public RetryConfig(int maxAttempts, long sleepInterval, long maxSleepTime,
51         TimeUnit timeUnit, BackoffPolicy backoffPolicy) {
52       this.maxAttempts = maxAttempts;
53       this.sleepInterval = sleepInterval;
54       this.maxSleepTime = maxSleepTime;
55       this.timeUnit = timeUnit;
56       this.backoffPolicy = backoffPolicy;
57     }
58 
setBackoffPolicy(BackoffPolicy backoffPolicy)59     public RetryConfig setBackoffPolicy(BackoffPolicy backoffPolicy) {
60       this.backoffPolicy = backoffPolicy;
61       return this;
62     }
63 
setMaxAttempts(int maxAttempts)64     public RetryConfig setMaxAttempts(int maxAttempts) {
65       this.maxAttempts = maxAttempts;
66       return this;
67     }
68 
setMaxSleepTime(long maxSleepTime)69     public RetryConfig setMaxSleepTime(long maxSleepTime) {
70       this.maxSleepTime = maxSleepTime;
71       return this;
72     }
73 
setSleepInterval(long sleepInterval)74     public RetryConfig setSleepInterval(long sleepInterval) {
75       this.sleepInterval = sleepInterval;
76       return this;
77     }
78 
setTimeUnit(TimeUnit timeUnit)79     public RetryConfig setTimeUnit(TimeUnit timeUnit) {
80       this.timeUnit = timeUnit;
81       return this;
82     }
83 
getMaxAttempts()84     public int getMaxAttempts() {
85       return maxAttempts;
86     }
87 
getMaxSleepTime()88     public long getMaxSleepTime() {
89       return maxSleepTime;
90     }
91 
getSleepInterval()92     public long getSleepInterval() {
93       return sleepInterval;
94     }
95 
getTimeUnit()96     public TimeUnit getTimeUnit() {
97       return timeUnit;
98     }
99 
getBackoffPolicy()100     public BackoffPolicy getBackoffPolicy() {
101       return backoffPolicy;
102     }
103   }
104 
105   /**
106    * Policy for calculating sleeping intervals between retry attempts
107    */
108   public static class BackoffPolicy {
getBackoffTime(RetryConfig config, int attempts)109     public long getBackoffTime(RetryConfig config, int attempts) {
110       return config.getSleepInterval();
111     }
112   }
113 
114   public static class ExponentialBackoffPolicy extends BackoffPolicy {
115     @Override
getBackoffTime(RetryConfig config, int attempts)116     public long getBackoffTime(RetryConfig config, int attempts) {
117       long backoffTime = (long) (config.getSleepInterval() * Math.pow(2, attempts));
118       return backoffTime;
119     }
120   }
121 
122   public static class ExponentialBackoffPolicyWithLimit extends ExponentialBackoffPolicy {
123     @Override
getBackoffTime(RetryConfig config, int attempts)124     public long getBackoffTime(RetryConfig config, int attempts) {
125       long backoffTime = super.getBackoffTime(config, attempts);
126       return config.getMaxSleepTime() > 0 ? Math.min(backoffTime, config.getMaxSleepTime()) : backoffTime;
127     }
128   }
129 
130   private static final Log LOG = LogFactory.getLog(RetryCounter.class);
131 
132   private RetryConfig retryConfig;
133   private int attempts;
134 
RetryCounter(int maxAttempts, long sleepInterval, TimeUnit timeUnit)135   public RetryCounter(int maxAttempts, long sleepInterval, TimeUnit timeUnit) {
136     this(new RetryConfig(maxAttempts, sleepInterval, -1, timeUnit, new ExponentialBackoffPolicy()));
137   }
138 
RetryCounter(RetryConfig retryConfig)139   public RetryCounter(RetryConfig retryConfig) {
140     this.attempts = 0;
141     this.retryConfig = retryConfig;
142   }
143 
getMaxAttempts()144   public int getMaxAttempts() {
145     return retryConfig.getMaxAttempts();
146   }
147 
148   /**
149    * Sleep for a back off time as supplied by the backoff policy, and increases the attempts
150    * @throws InterruptedException
151    */
sleepUntilNextRetry()152   public void sleepUntilNextRetry() throws InterruptedException {
153     int attempts = getAttemptTimes();
154     long sleepTime = retryConfig.backoffPolicy.getBackoffTime(retryConfig, attempts);
155     if (LOG.isTraceEnabled()) {
156       LOG.trace("Sleeping " + sleepTime + "ms before retry #" + attempts + "...");
157     }
158     retryConfig.getTimeUnit().sleep(sleepTime);
159     useRetry();
160   }
161 
shouldRetry()162   public boolean shouldRetry() {
163     return attempts < retryConfig.getMaxAttempts();
164   }
165 
useRetry()166   public void useRetry() {
167     attempts++;
168   }
169 
isRetry()170   public boolean isRetry() {
171     return attempts > 0;
172   }
173 
getAttemptTimes()174   public int getAttemptTimes() {
175     return attempts;
176   }
177 }
178