View Javadoc

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  
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   * Runs an rpc'ing {@link RetryingCallable}. Sets into rpc client
44   * threadlocal outstanding timeouts as so we don't persist too much.
45   * Dynamic rather than static so can set the generic appropriately.
46   *
47   * This object has a state. It should not be used by in parallel by different threads.
48   * Reusing it is possible however, even between multiple threads. However, the user will
49   *  have to manage the synchronization on its side: there is no synchronization inside the class.
50   */
51  @InterfaceAudience.Private
52  public class RpcRetryingCaller<T> {
53    public static final Log LOG = LogFactory.getLog(RpcRetryingCaller.class);
54    /**
55     * When we started making calls.
56     */
57    private long globalStartTime;
58    /**
59     * Start and end times for a single call.
60     */
61    private final static int MIN_RPC_TIMEOUT = 1;
62    /** How many retries are allowed before we start to log */
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;// timeout for each rpc request
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          // If there is no time left, we're trying anyway. It's too late.
99          // 0 means no timeout, and it's not the intent here. So we secure both cases by
100         // resetting to the minimum.
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    * Retries if invocation fails.
124    * @param callTimeout Timeout for this call
125    * @param callable The {@link RetryingCallable} to run.
126    * @return an object of type T
127    * @throws IOException if a remote or network exception occurs
128    * @throws RuntimeException other unspecified error
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         // bad cache entries are cleared in the call to RetryingCallable#throwable() in catch block
140         callable.prepare(tries != 0); // if called with false, check table status on ZK
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         // translateException throws exception when should not retry: i.e. when request is bad.
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         // If the server is dead, we need to wait a little before retrying, to give
167         // a chance to the regions to be moved
168         // get right pause time, start by RETRY_BACKOFF[0] * pauseBase, where pauseBase might be
169         // special when encountering CallQueueTooBigException, see #HBASE-17114
170         long pauseBase = (t instanceof CallQueueTooBigException) ? pauseForCQTBE : pause;
171         expectedSleep = callable.sleep(pauseBase, tries);
172 
173         // If, after the planned sleep, there won't be enough time left, we stop now.
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    * @return Calculate how long a single call took
199    */
200   private long singleCallDuration(final long expectedSleep) {
201     return (EnvironmentEdgeManager.currentTime() - this.globalStartTime) + expectedSleep;
202   }
203 
204   /**
205    * Call the server once only.
206    * {@link RetryingCallable} has a strange shape so we can do retrys.  Use this invocation if you
207    * want to do a single call only (A call to {@link RetryingCallable#call(int)} will not likely
208    * succeed).
209    * @return an object of type T
210    * @throws IOException if a remote or network exception occurs
211    * @throws RuntimeException other unspecified error
212    */
213   public T callWithoutRetries(RetryingCallable<T> callable, int callTimeout)
214   throws IOException, RuntimeException {
215     // The code of this method should be shared with withRetries.
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       // It would be nice to clear the location cache here.
224       if (t2 instanceof IOException) {
225         throw (IOException)t2;
226       } else {
227         throw new RuntimeException(t2);
228       }
229     }
230   }
231 
232   /**
233    * Get the good or the remote exception if any, throws the DoNotRetryIOException.
234    * @param t the throwable to analyze
235    * @return the translated exception, if it's not a DoNotRetryIOException
236    * @throws DoNotRetryIOException - if we find it, we throw it instead of translating.
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       // Don't let ServiceException out; its rpc specific.
261       t = cause;
262       // t could be a RemoteException so go aaround again.
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 }