Java tutorial
/** * Copyright 2010 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.wasp.client; import com.alibaba.wasp.EntityGroupLocation; import com.alibaba.wasp.FConstants; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.ipc.RemoteException; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; /** * Abstract class that implements {@link java.util.concurrent.Callable}. * Implementation stipulates return type and method we actually invoke on remote * Server. Usually used inside a try/catch that fields usual connection failures * all wrapped up in a retry loop. * <p> * Call {@link #connect(boolean)} to connect to server hosting entityGroup that * contains the passed row in the passed table before invoking {@link #call()}. * * @see FConnection#getFServerWithoutRetries(ServerCallable) * @param <T> * the class that the ServerCallable handles */ public abstract class ServerCallable<T> implements Callable<T> { public final Log LOG = LogFactory.getLog(this.getClass()); protected final FConnection connection; protected final byte[] tableName; protected final byte[] row; protected EntityGroupLocation location; protected ClientProtocol server; protected int callTimeout; protected long startTime, endTime; protected long pause; /** * @param connection * Connection to use. * @param tableName * Table name to which <code>row</code> belongs. * @param row * The row we want in <code>tableName</code>. */ public ServerCallable(FConnection connection, byte[] tableName, byte[] row) { this(connection, tableName, row, FConstants.DEFAULT_WASP_CLIENT_OPERATION_TIMEOUT); } public ServerCallable(FConnection connection, byte[] tableName, byte[] row, int callTimeout) { this.connection = connection; this.tableName = tableName; this.row = row; this.callTimeout = callTimeout; this.pause = connection.getConfiguration().getLong(FConstants.WASP_CLIENT_PAUSE, FConstants.DEFAULT_WASP_CLIENT_PAUSE); } /** * Connect to the server hosting entityGroup with row from table name. * * @param reload * Set this to true if connection should re-find the entityGroup * @throws java.io.IOException * e */ public void connect(final boolean reload) throws IOException { this.location = connection.getEntityGroupLocation(tableName, row, reload); this.server = connection.getClient(location.getHostname(), location.getPort()); } public void beforeCall() { this.startTime = System.currentTimeMillis(); } public void afterCall() { this.endTime = System.currentTimeMillis(); } public void shouldRetry(Throwable throwable) throws IOException { if (this.callTimeout != FConstants.DEFAULT_WASP_CLIENT_OPERATION_TIMEOUT) if (throwable instanceof SocketTimeoutException || (this.endTime - this.startTime > this.callTimeout)) { throw (SocketTimeoutException) (SocketTimeoutException) new SocketTimeoutException( "Call to access row '" + Bytes.toString(row) + "' on table '" + Bytes.toString(tableName) + "' failed on socket timeout exception: " + throwable).initCause(throwable); } else { this.callTimeout = ((int) (this.endTime - this.startTime)); } } /** * @return {@link FConnection} instance used by this Callable. */ FConnection getConnection() { return this.connection; } /** * Run this instance with retries, timed waits, and refinds of missing * entityGroups. * * @return an object of type T * @throws java.io.IOException * if a remote or network exception occurs * @throws RuntimeException * other unspecified error */ public T withRetries() throws IOException, RuntimeException { Configuration c = getConnection().getConfiguration(); final int numRetries = c.getInt(FConstants.WASP_CLIENT_RETRIES_NUMBER, FConstants.DEFAULT_WASP_CLIENT_RETRIES_NUMBER); List<RetriesExhaustedException.ThrowableWithExtraContext> exceptions = new ArrayList<RetriesExhaustedException.ThrowableWithExtraContext>(); for (int tries = 0; tries < numRetries; tries++) { try { beforeCall(); connect(tries != 0); return call(); } catch (Throwable t) { shouldRetry(t); t = translateException(t); if (t instanceof SocketTimeoutException || t instanceof ConnectException || t instanceof RetriesExhaustedException) { // if thrown these exceptions, we clear all the cache entries that // map to that slow/dead server; otherwise, let cache miss and ask // .FMETA. again to find the new location EntityGroupLocation egl = location; if (egl != null) { getConnection().clearCaches(egl.getHostnamePort()); } } if (t instanceof RuntimeException) { throw new RuntimeException(t); } RetriesExhaustedException.ThrowableWithExtraContext qt = new RetriesExhaustedException.ThrowableWithExtraContext( t, System.currentTimeMillis(), toString()); exceptions.add(qt); long pauseTime = ConnectionUtils.getPauseTime(this.pause, tries); LOG.info("withRetries attempt " + tries + " of " + numRetries + " failed; retrying after sleep of " + pauseTime); try { Thread.sleep(pauseTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Thread was interrupted while trying to connect to FServer.", e); } if (tries == numRetries - 1) { throw new RetriesExhaustedException(tries, exceptions); } } finally { afterCall(); } } return null; } /** * Run this instance against the server once. * * @return an object of type T * @throws java.io.IOException * if a remote or network exception occurs * @throws RuntimeException * other unspecified error */ public T withoutRetries() throws IOException, RuntimeException { try { beforeCall(); connect(false); return call(); } catch (Throwable t) { Throwable t2 = translateException(t); if (t2 instanceof IOException) { throw (IOException) t2; } else { throw new RuntimeException(t2); } } finally { afterCall(); } } private static Throwable translateException(Throwable t) throws IOException { if (t instanceof UndeclaredThrowableException) { t = t.getCause(); } if (t instanceof RemoteException) { t = ((RemoteException) t).unwrapRemoteException(); } if (t instanceof DoNotRetryIOException) { throw (DoNotRetryIOException) t; } if (t instanceof IOException) { RuntimeException runtimeException = unwrapRuntimeException(t); if (runtimeException != null) { return runtimeException; } } return t; } private static RuntimeException unwrapRuntimeException(Throwable t) { if (StringUtils.isNotEmpty(t.getMessage())) { try { Class exceptionClass = Class.forName(t.getMessage()); Constructor cn = exceptionClass.getConstructor(String.class); cn.setAccessible(true); String firstLine = t.getMessage(); Object ex = cn.newInstance(firstLine); if (ex instanceof RuntimeException) { return (RuntimeException) ex; } } catch (ClassNotFoundException e) { //ignore } catch (NoSuchMethodException e) { //ignore } catch (InvocationTargetException e) { //ignore } catch (InstantiationException e) { //ignore } catch (IllegalAccessException e) { //ignore } } return null; } }