View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.client;
22  
23  import java.io.IOException;
24  import java.lang.reflect.UndeclaredThrowableException;
25  import java.net.ConnectException;
26  import java.net.SocketTimeoutException;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.concurrent.Callable;
30  
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.DoNotRetryIOException;
33  import org.apache.hadoop.hbase.HConstants;
34  import org.apache.hadoop.hbase.HRegionLocation;
35  import org.apache.hadoop.hbase.NotServingRegionException;
36  import org.apache.hadoop.hbase.ipc.HBaseRPC;
37  import org.apache.hadoop.hbase.ipc.HRegionInterface;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.ipc.RemoteException;
40  
41  /**
42   * Abstract class that implements {@link Callable}.  Implementation stipulates
43   * return type and method we actually invoke on remote Server.  Usually
44   * used inside a try/catch that fields usual connection failures all wrapped
45   * up in a retry loop.
46   * <p>Call {@link #connect(boolean)} to connect to server hosting region
47   * that contains the passed row in the passed table before invoking
48   * {@link #call()}.
49   * @see HConnection#getRegionServerWithoutRetries(ServerCallable)
50   * @param <T> the class that the ServerCallable handles
51   */
52  public abstract class ServerCallable<T> implements Callable<T> {
53    protected final HConnection connection;
54    protected final byte [] tableName;
55    protected final byte [] row;
56    protected HRegionLocation location;
57    protected HRegionInterface server;
58    protected int callTimeout;
59    protected long startTime, endTime;
60  
61    /**
62     * @param connection Connection to use.
63     * @param tableName Table name to which <code>row</code> belongs.
64     * @param row The row we want in <code>tableName</code>.
65     */
66    public ServerCallable(HConnection connection, byte [] tableName, byte [] row) {
67      this(connection, tableName, row, HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT);
68    }
69  
70    public ServerCallable(HConnection connection, byte [] tableName, byte [] row, int callTimeout) {
71      this.connection = connection;
72      this.tableName = tableName;
73      this.row = row;
74      this.callTimeout = callTimeout;
75    }
76  
77    /**
78     * Connect to the server hosting region with row from tablename.
79     * @param reload Set this to true if connection should re-find the region
80     * @throws IOException e
81     */
82    public void connect(final boolean reload) throws IOException {
83      this.location = connection.getRegionLocation(tableName, row, reload);
84      this.server = connection.getHRegionConnection(location.getHostname(),
85        location.getPort());
86    }
87  
88    /** @return the server name
89     * @deprecated Just use {@link #toString()} instead.
90     */
91    public String getServerName() {
92      if (location == null) return null;
93      return location.getHostnamePort();
94    }
95  
96    /** @return the region name
97     * @deprecated Just use {@link #toString()} instead.
98     */
99    public byte[] getRegionName() {
100     if (location == null) return null;
101     return location.getRegionInfo().getRegionName();
102   }
103 
104   /** @return the row
105    * @deprecated Just use {@link #toString()} instead.
106    */
107   public byte [] getRow() {
108     return row;
109   }
110 
111   public void beforeCall() {
112     HBaseRPC.setRpcTimeout(this.callTimeout);
113     this.startTime = System.currentTimeMillis();
114   }
115 
116   public void afterCall() {
117     HBaseRPC.resetRpcTimeout();
118     this.endTime = System.currentTimeMillis();
119   }
120 
121   public void shouldRetry(Throwable throwable) throws IOException {
122     if (this.callTimeout != HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT)
123       if (throwable instanceof SocketTimeoutException
124           || (this.endTime - this.startTime > this.callTimeout)) {
125         throw (SocketTimeoutException) (SocketTimeoutException) new SocketTimeoutException(
126             "Call to access row '" + Bytes.toString(row) + "' on table '"
127                 + Bytes.toString(tableName)
128                 + "' failed on socket timeout exception: " + throwable)
129             .initCause(throwable);
130       } else {
131         this.callTimeout = ((int) (this.endTime - this.startTime));
132       }
133   }
134 
135   /**
136    * @return {@link HConnection} instance used by this Callable.
137    */
138   HConnection getConnection() {
139     return this.connection;
140   }
141 
142   /**
143    * Run this instance with retries, timed waits,
144    * and refinds of missing regions.
145    *
146    * @param <T> the type of the return value
147    * @return an object of type T
148    * @throws IOException if a remote or network exception occurs
149    * @throws RuntimeException other unspecified error
150    */
151   public T withRetries()
152   throws IOException, RuntimeException {
153     Configuration c = getConnection().getConfiguration();
154     final long pause = c.getLong(HConstants.HBASE_CLIENT_PAUSE,
155       HConstants.DEFAULT_HBASE_CLIENT_PAUSE);
156     final int numRetries = c.getInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
157       HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER);
158     List<RetriesExhaustedException.ThrowableWithExtraContext> exceptions =
159       new ArrayList<RetriesExhaustedException.ThrowableWithExtraContext>();
160     for (int tries = 0; tries < numRetries; tries++) {
161       try {
162         beforeCall();
163         connect(tries != 0);
164         return call();
165       } catch (Throwable t) {
166         shouldRetry(t);
167         t = translateException(t);
168         if (t instanceof SocketTimeoutException ||
169             t instanceof ConnectException ||
170             t instanceof RetriesExhaustedException) {
171           // if thrown these exceptions, we clear all the cache entries that
172           // map to that slow/dead server; otherwise, let cache miss and ask
173           // .META. again to find the new location
174           HRegionLocation hrl = location;
175           if (hrl != null) {
176             getConnection().clearCaches(hrl.getHostnamePort());
177           }
178         } else if (t instanceof NotServingRegionException && numRetries == 1) {
179           // Purge cache entries for this specific region from META cache
180           // since we don't call connect(true) when number of retries is 1.
181           getConnection().deleteCachedRegionLocation(location);
182         }
183         RetriesExhaustedException.ThrowableWithExtraContext qt =
184           new RetriesExhaustedException.ThrowableWithExtraContext(t,
185             System.currentTimeMillis(), toString());
186         exceptions.add(qt);
187         if (tries == numRetries - 1) {
188           throw new RetriesExhaustedException(tries, exceptions);
189         }
190       } finally {
191         afterCall();
192       }
193       try {
194         Thread.sleep(ConnectionUtils.getPauseTime(pause, tries));
195       } catch (InterruptedException e) {
196         Thread.currentThread().interrupt();
197         throw new IOException("Giving up after tries=" + tries, e);
198       }
199     }
200     return null;
201   }
202 
203   /**
204    * Run this instance against the server once.
205    * @param <T> the type of the return value
206    * @return an object of type T
207    * @throws IOException if a remote or network exception occurs
208    * @throws RuntimeException other unspecified error
209    */
210   public T withoutRetries()
211   throws IOException, RuntimeException {
212     try {
213       beforeCall();
214       connect(false);
215       return call();
216     } catch (Throwable t) {
217       Throwable t2 = translateException(t);
218       if (t2 instanceof IOException) {
219         throw (IOException)t2;
220       } else {
221         throw new RuntimeException(t2);
222       }
223     } finally {
224       afterCall();
225     }
226   }
227 
228   private static Throwable translateException(Throwable t) throws IOException {
229     if (t instanceof UndeclaredThrowableException) {
230       t = t.getCause();
231     }
232     if (t instanceof RemoteException) {
233       t = ((RemoteException)t).unwrapRemoteException();
234     }
235     if (t instanceof DoNotRetryIOException) {
236       throw (DoNotRetryIOException)t;
237     }
238     return t;
239   }
240 }