1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.client;
21
22 import java.io.IOException;
23 import java.io.InterruptedIOException;
24 import java.lang.reflect.UndeclaredThrowableException;
25 import java.net.SocketTimeoutException;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.concurrent.atomic.AtomicBoolean;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hadoop.hbase.classification.InterfaceAudience;
33 import org.apache.hadoop.hbase.CallQueueTooBigException;
34 import org.apache.hadoop.hbase.DoNotRetryIOException;
35 import org.apache.hadoop.hbase.exceptions.PreemptiveFastFailException;
36 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
37 import org.apache.hadoop.hbase.util.ExceptionUtil;
38 import org.apache.hadoop.ipc.RemoteException;
39
40 import com.google.protobuf.ServiceException;
41
42
43
44
45
46
47
48
49
50
51 @InterfaceAudience.Private
52 public class RpcRetryingCaller<T> {
53 public static final Log LOG = LogFactory.getLog(RpcRetryingCaller.class);
54
55
56
57 private long globalStartTime;
58
59
60
61 private final static int MIN_RPC_TIMEOUT = 1;
62
63 private final int startLogErrorsCnt;
64
65 private final long pause;
66 private final long pauseForCQTBE;
67 private final int retries;
68 private final int rpcTimeout;
69 private final Object lock = new Object();
70 private final AtomicBoolean cancelled = new AtomicBoolean(false);
71 private final RetryingCallerInterceptor interceptor;
72 private final RetryingCallerInterceptorContext context;
73
74 public RpcRetryingCaller(long pause, long pauseForCQTBE, int retries, int startLogErrorsCnt) {
75 this(pause, pauseForCQTBE, retries, RetryingCallerInterceptorFactory.NO_OP_INTERCEPTOR,
76 startLogErrorsCnt, 0);
77 }
78
79 public RpcRetryingCaller(long pause, long pauseForCQTBE, int retries,
80 RetryingCallerInterceptor interceptor, int startLogErrorsCnt, int rpcTimeout) {
81 this.pause = pause;
82 this.pauseForCQTBE = pauseForCQTBE;
83 this.retries = retries;
84 this.interceptor = interceptor;
85 context = interceptor.createEmptyContext();
86 this.startLogErrorsCnt = startLogErrorsCnt;
87 this.rpcTimeout = rpcTimeout;
88 }
89
90 private int getRemainingTime(int callTimeout) {
91 if (callTimeout <= 0) {
92 return 0;
93 } else {
94 if (callTimeout == Integer.MAX_VALUE) return Integer.MAX_VALUE;
95 int remainingTime = (int) (callTimeout -
96 (EnvironmentEdgeManager.currentTime() - this.globalStartTime));
97 if (remainingTime < MIN_RPC_TIMEOUT) {
98
99
100
101 remainingTime = MIN_RPC_TIMEOUT;
102 }
103 return remainingTime;
104 }
105 }
106
107 private int getTimeout(int callTimeout){
108 int timeout = getRemainingTime(callTimeout);
109 if (timeout <= 0 || (rpcTimeout > 0 && rpcTimeout < timeout)){
110 timeout = rpcTimeout;
111 }
112 return timeout;
113 }
114
115 public void cancel(){
116 synchronized (lock){
117 cancelled.set(true);
118 lock.notifyAll();
119 }
120 }
121
122
123
124
125
126
127
128
129
130 public T callWithRetries(RetryingCallable<T> callable, int callTimeout)
131 throws IOException, RuntimeException {
132 List<RetriesExhaustedException.ThrowableWithExtraContext> exceptions =
133 new ArrayList<RetriesExhaustedException.ThrowableWithExtraContext>();
134 this.globalStartTime = EnvironmentEdgeManager.currentTime();
135 context.clear();
136 for (int tries = 0;; tries++) {
137 long expectedSleep;
138 try {
139
140 callable.prepare(tries != 0);
141 interceptor.intercept(context.prepare(callable, tries));
142 return callable.call(getTimeout(callTimeout));
143 } catch (PreemptiveFastFailException e) {
144 throw e;
145 } catch (Throwable t) {
146 ExceptionUtil.rethrowIfInterrupt(t);
147
148
149 interceptor.handleFailure(context, t);
150 t = translateException(t);
151 if (tries > startLogErrorsCnt) {
152 LOG.info("Call exception, tries=" + tries + ", retries=" + retries + ", started=" +
153 (EnvironmentEdgeManager.currentTime() - this.globalStartTime) + " ms ago, "
154 + "cancelled=" + cancelled.get() + ", msg="
155 + t.getMessage() + " " + callable.getExceptionMessageAdditionalDetail());
156 }
157
158 callable.throwable(t, retries != 1);
159 RetriesExhaustedException.ThrowableWithExtraContext qt =
160 new RetriesExhaustedException.ThrowableWithExtraContext(t,
161 EnvironmentEdgeManager.currentTime(), toString());
162 exceptions.add(qt);
163 if (tries >= retries - 1) {
164 throw new RetriesExhaustedException(tries, exceptions);
165 }
166
167
168
169
170 long pauseBase = (t instanceof CallQueueTooBigException) ? pauseForCQTBE : pause;
171 expectedSleep = callable.sleep(pauseBase, tries);
172
173
174 long duration = singleCallDuration(expectedSleep);
175 if (duration > callTimeout) {
176 String msg = "callTimeout=" + callTimeout + ", callDuration=" + duration +
177 ": " + t.getMessage() + " " + callable.getExceptionMessageAdditionalDetail();
178 throw (SocketTimeoutException)(new SocketTimeoutException(msg).initCause(t));
179 }
180 } finally {
181 interceptor.updateFailureInfo(context);
182 }
183 try {
184 if (expectedSleep > 0) {
185 synchronized (lock) {
186 if (cancelled.get()) return null;
187 lock.wait(expectedSleep);
188 }
189 }
190 if (cancelled.get()) return null;
191 } catch (InterruptedException e) {
192 throw new InterruptedIOException("Interrupted after " + tries + " tries on " + retries);
193 }
194 }
195 }
196
197
198
199
200 private long singleCallDuration(final long expectedSleep) {
201 return (EnvironmentEdgeManager.currentTime() - this.globalStartTime) + expectedSleep;
202 }
203
204
205
206
207
208
209
210
211
212
213 public T callWithoutRetries(RetryingCallable<T> callable, int callTimeout)
214 throws IOException, RuntimeException {
215
216 this.globalStartTime = EnvironmentEdgeManager.currentTime();
217 try {
218 callable.prepare(false);
219 return callable.call(callTimeout);
220 } catch (Throwable t) {
221 Throwable t2 = translateException(t);
222 ExceptionUtil.rethrowIfInterrupt(t2);
223
224 if (t2 instanceof IOException) {
225 throw (IOException)t2;
226 } else {
227 throw new RuntimeException(t2);
228 }
229 }
230 }
231
232
233
234
235
236
237
238 static Throwable translateException(Throwable t) throws DoNotRetryIOException {
239 if (t instanceof UndeclaredThrowableException) {
240 if (t.getCause() != null) {
241 t = t.getCause();
242 }
243 }
244 if (t instanceof RemoteException) {
245 t = ((RemoteException)t).unwrapRemoteException();
246 }
247 if (t instanceof LinkageError) {
248 throw new DoNotRetryIOException(t);
249 }
250 if (t instanceof ServiceException) {
251 ServiceException se = (ServiceException)t;
252 Throwable cause = se.getCause();
253 if (cause != null) {
254 if (cause instanceof DoNotRetryIOException) {
255 throw (DoNotRetryIOException)cause;
256 } else if (cause instanceof NeedUnmanagedConnectionException) {
257 throw new DoNotRetryIOException(cause);
258 }
259 }
260
261 t = cause;
262
263 translateException(t);
264 } else if (t instanceof DoNotRetryIOException) {
265 throw (DoNotRetryIOException)t;
266 } else if (t instanceof NeedUnmanagedConnectionException) {
267 throw new DoNotRetryIOException(t);
268 }
269 return t;
270 }
271
272 @Override
273 public String toString() {
274 return "RpcRetryingCaller{" + "globalStartTime=" + globalStartTime +
275 ", pause=" + pause + ", retries=" + retries + '}';
276 }
277 }