Package com.alibaba.wasp.client

Source Code of com.alibaba.wasp.client.FConnectionManager$FConnectionKey

/**
*
* 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.*;
import com.alibaba.wasp.conf.WaspConfiguration;
import com.alibaba.wasp.fserver.AdminProtocol;
import com.alibaba.wasp.ipc.VersionedProtocol;
import com.alibaba.wasp.ipc.WaspRPC;
import com.alibaba.wasp.master.FMasterAdminProtocol;
import com.alibaba.wasp.master.FMasterMonitorProtocol;
import com.alibaba.wasp.master.FMetaServerProtocol;
import com.alibaba.wasp.master.MasterProtocol;
import com.alibaba.wasp.meta.FMetaReader;
import com.alibaba.wasp.meta.FTable;
import com.alibaba.wasp.meta.TableSchemaCacheReader;
import com.alibaba.wasp.protobuf.ProtobufUtil;
import com.alibaba.wasp.protobuf.RequestConverter;
import com.alibaba.wasp.protobuf.generated.MasterMonitorProtos.GetTableDescriptorsRequest;
import com.alibaba.wasp.protobuf.generated.MasterMonitorProtos.GetTableDescriptorsResponse;
import com.alibaba.wasp.zookeeper.MasterAddressTracker;
import com.alibaba.wasp.zookeeper.ZKTableReadOnly;
import com.alibaba.wasp.zookeeper.ZKUtil;
import com.alibaba.wasp.zookeeper.ZooKeeperWatcher;
import com.google.protobuf.ServiceException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Chore;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.Stoppable;
import org.apache.hadoop.hbase.util.Addressing;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.SoftValueSortedMap;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.zookeeper.KeeperException;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.*;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

public class FConnectionManager {

  private static final Log LOG = LogFactory.getLog(FConnectionManager.class);

  // An LRU Map of FConnectionKey -> FConnection (TableServer). All
  // access must be synchronized. This map is not private because tests
  // need to be able to tinker with it.
  static final Map<FConnectionKey, FConnectionImplementation> WASP_INSTANCES;

  public static final int MAX_CACHED_WASP_INSTANCES;

  static {
    // We set instances to one more than the value specified for {@link
    // FConstants#ZOOKEEPER_MAX_CLIENT_CNXNS}. By default, the zk default max
    // connections to the ensemble from the one client is 30, so in that case we
    // should run into zk issues before the LRU hit this value of 31.
    MAX_CACHED_WASP_INSTANCES = WaspConfiguration.create().getInt(
        FConstants.ZOOKEEPER_MAX_CLIENT_CNXNS,
        FConstants.DEFAULT_ZOOKEPER_MAX_CLIENT_CNXNS) + 1;
    WASP_INSTANCES = new LinkedHashMap<FConnectionKey, FConnectionImplementation>(
        (int) (MAX_CACHED_WASP_INSTANCES / 0.75F) + 1, 0.75F, true) {

      private static final long serialVersionUID = -17756812924832264L;

      @Override
      protected boolean removeEldestEntry(
          Entry<FConnectionKey, FConnectionImplementation> eldest) {
        return size() > MAX_CACHED_WASP_INSTANCES;
      }
    };
  }

  /**
   * Get the connection that goes with the passed <code>conf</code>
   * configuration instance. If no current connection exists, method creates a
   * new connection for the passed <code>conf</code> instance.
   *
   * @param conf
   *          configuration
   * @return HConnection object for <code>conf</code>
   * @throws com.alibaba.wasp.ZooKeeperConnectionException
   */
  public static FConnection getConnection(Configuration conf)
      throws ZooKeeperConnectionException {
    FConnectionKey connectionKey = new FConnectionKey(conf);
    synchronized (WASP_INSTANCES) {
      FConnectionImplementation connection = WASP_INSTANCES.get(connectionKey);
      if (connection == null) {
        connection = new FConnectionImplementation(conf, true);
        WASP_INSTANCES.put(connectionKey, connection);
      }
      connection.incCount();
      return connection;
    }
  }

  static class FConnectionImplementation implements FConnection, Closeable {

    static final Log LOG = LogFactory.getLog(FConnectionImplementation.class);

    private final Class<? extends AdminProtocol> adminClass;

    private final Class<? extends ClientProtocol> clientClass;

    /** Parameter name for what client protocol to use. */
    public static final String CLIENT_PROTOCOL_CLASS = "wasp.clientprotocol.class";

    /** Default client protocol class name. */
    public static final String DEFAULT_CLIENT_PROTOCOL_CLASS = ClientProtocol.class
        .getName();

    /** Parameter name for what admin protocol to use. */
    public static final String ADMIN_PROTOCOL_CLASS = "wasp.adminprotocol.class";

    /** Default admin protocol class name. */
    public static final String DEFAULT_ADMIN_PROTOCOL_CLASS = AdminProtocol.class
        .getName();

    private final int numRetries;
    private final int rpcTimeout;
    private final int maxRPCAttempts;
    private final long pause;

    private final Configuration conf;

    private volatile boolean closed;
    private volatile boolean aborted;

    private volatile ZooKeeperWatcher zooKeeper;

    private volatile ClusterId clusterId;

    private final Object entityGroupLock = new Object();

    // indicates whether this connection's life cycle is managed
    private final boolean managed;

    // ZooKeeper-based master address tracker
    private volatile MasterAddressTracker masterAddressTracker;

    // Known entityGroup ServerName.toString() -> EntityGroup Client/Admin
    private final ConcurrentHashMap<String, Map<String, VersionedProtocol>> servers = new ConcurrentHashMap<String, Map<String, VersionedProtocol>>();

    private final ConcurrentHashMap<String, String> connectionLock = new ConcurrentHashMap<String, String>();

    private boolean stopProxy = true;

    private int refCount;

    private final Object masterAndZKLock = new Object();

    // keepAlive time, in ms. No reason to make it configurable.
    private static final long keepAlive = 5 * 60 * 1000;

    private final DelayedClosing delayedClosing = DelayedClosing
        .createAndStart(this);

    /**
     * constructor
     *
     * @param conf
     *          Configuration object
     */
    @SuppressWarnings("unchecked")
    public FConnectionImplementation(Configuration conf, boolean managed)
        throws ZooKeeperConnectionException {
      this.conf = conf;
      this.managed = managed;
      this.closed = false;
      try {
        String adminClassName = conf.get(ADMIN_PROTOCOL_CLASS,
            DEFAULT_ADMIN_PROTOCOL_CLASS);
        this.adminClass = (Class<? extends AdminProtocol>) Class
            .forName(adminClassName);
        String clientClassName = conf.get(CLIENT_PROTOCOL_CLASS,
            DEFAULT_CLIENT_PROTOCOL_CLASS);

        try {
          this.clientClass = (Class<? extends ClientProtocol>) Class
              .forName(clientClassName);
        } catch (ClassNotFoundException e) {
          throw new UnsupportedOperationException(
              "Unable to find client protocol " + clientClassName, e);
        }
      } catch (ClassNotFoundException e) {
        throw new UnsupportedOperationException(e);
      }

      this.numRetries = conf.getInt(FConstants.WASP_CLIENT_RETRIES_NUMBER,
          FConstants.DEFAULT_WASP_CLIENT_RETRIES_NUMBER);
      this.rpcTimeout = conf.getInt(FConstants.WASP_RPC_TIMEOUT_KEY,
          FConstants.DEFAULT_WASP_RPC_TIMEOUT);
      this.maxRPCAttempts = conf.getInt(FConstants.WASP_CLIENT_RPC_MAXATTEMPTS,
          FConstants.DEFAULT_WASP_CLIENT_RPC_MAXATTEMPTS);
      this.pause = conf.getLong(FConstants.WASP_CLIENT_PAUSE,
          FConstants.DEFAULT_WASP_CLIENT_PAUSE);
    }

    /**
     * Map of table to table {@link com.alibaba.wasp.EntityGroupLocation}s. The
     * table key is made by doing a
     * {@link org.apache.hadoop.hbase.util.Bytes#mapKey(byte[])} of the table's
     * name.
     */
    private final Map<Integer, SoftValueSortedMap<byte[], EntityGroupLocation>> cachedEntityGroupLocations = new HashMap<Integer, SoftValueSortedMap<byte[], EntityGroupLocation>>();

    // The presence of a server in the map implies it's likely that there is an
    // entry in cachedEntityGroupLocations that map to this server; but the
    // absence
    // of a server in this map guarentees that there is no entry in cache that
    // maps to the absent server.
    private final Set<String> cachedServers = new HashSet<String>();

    // entityGroup cache prefetch is enabled by default. this set contains all
    // tables whose entityGroup cache prefetch are disabled.
    private final Set<Integer> entityGroupCachePrefetchDisabledTables = new CopyOnWriteArraySet<Integer>();

    @Override
    public Configuration getConfiguration() {
      return this.conf;
    }

    @Override
    public ZooKeeperWatcher getZooKeeperWatcher()
        throws ZooKeeperConnectionException {
      if (zooKeeper == null) {
        try {
          this.zooKeeper = new ZooKeeperWatcher(conf, "fconnection", this);
        } catch (ZooKeeperConnectionException zce) {
          throw zce;
        } catch (IOException e) {
          throw new ZooKeeperConnectionException("An error is preventing"
              + " Wasp from connecting to ZooKeeper", e);
        }
      }
      return zooKeeper;
    }

    private synchronized void ensureZookeeperTrackers()
        throws ZooKeeperConnectionException {
      // initialize zookeeper and master address manager
      if (zooKeeper == null) {
        zooKeeper = getZooKeeperWatcher();
      }
      if (clusterId == null) {
        clusterId = new ClusterId();
      }
      if (masterAddressTracker == null) {
        masterAddressTracker = new MasterAddressTracker(zooKeeper, this);
        masterAddressTracker.start();
      }
    }

    private synchronized void resetZooKeeperTrackers() {
      if (masterAddressTracker != null) {
        masterAddressTracker.stop();
        masterAddressTracker = null;
      }
      clusterId = null;
      if (zooKeeper != null) {
        zooKeeper.close();
        zooKeeper = null;
      }
    }

    @Override
    public boolean isMasterRunning() throws MasterNotRunningException,
        ZooKeeperConnectionException {
      getKeepAliveMasterMonitor().close();
      return true;
    }

    @Override
    public boolean isTableEnabled(byte[] tableName) throws IOException {
      FTable.isLegalTableName(Bytes.toString(tableName));
      return testTableOnlineState(tableName, true);
    }

    /*
     * @param True if table is online
     */
    private boolean testTableOnlineState(byte[] tableName, boolean online)
        throws IOException {
      getZooKeeperWatcher();
      String tableNameStr = Bytes.toString(tableName);
      try {
        if (online) {
          return ZKTableReadOnly.isEnabledTable(this.zooKeeper, tableNameStr);
        }
        return ZKTableReadOnly.isDisabledTable(this.zooKeeper, tableNameStr);
      } catch (KeeperException e) {
        throw new IOException("Enable/Disable failed", e);
      }
    }

    /**
     * Return if this client has no reference
     *
     * @return true if this client has no reference; false otherwise
     */
    boolean isZeroReference() {
      return refCount == 0;
    }

    /**
     * Increment this client's reference count.
     */
    void incCount() {
      ++refCount;
    }

    /**
     * Decrement this client's reference count.
     */
    void decCount() {
      if (refCount > 0) {
        --refCount;
      }
    }

    @Override
    public boolean isTableDisabled(byte[] tableName) throws IOException {
      FTable.isLegalTableName(Bytes.toString(tableName));
      return testTableOnlineState(tableName, false);
    }

    @Override
    public boolean isTableAvailable(final byte[] tableName) throws IOException {
      if (!isTableEnabled(tableName)) {
        return false;
      }
      try {
        FTable fTable = this.getFTableDescriptor(tableName);
        byte[] rootTableName = null;
        if (fTable != null) {
          rootTableName = fTable.isRootTable() ? tableName : Bytes
              .toBytes(fTable.getParentName());
        }
        if (rootTableName == null || !isTableEnabled(rootTableName)) {
          return false;
        }
        return this
            .getKeepAliveMasterAdmin()
            .isTableAvailable(null,
                RequestConverter.buildIsTableAvailableRequest(rootTableName))
            .getAvailable();
      } catch (ServiceException e) {
        throw new IOException(e);
      }
    }

    @Override
    public boolean isTableLocked(final byte[] tableName) throws IOException {
      try {
        return this
            .getKeepAliveMasterAdmin()
            .isTableLocked(null,
                RequestConverter.buildIsTableLockedRequest(tableName))
            .getLocked();
      } catch (ServiceException e) {
        throw new IOException(e);
      }
    }

    @Override
    public FTable[] listTables() throws IOException {
      MasterMonitorKeepAliveConnection master = getKeepAliveMasterMonitor();
      try {
        GetTableDescriptorsRequest req = RequestConverter
            .buildGetTableDescriptorsRequest(null);
        return ProtobufUtil.getHTableDescriptorArray(master
            .getTableDescriptors(null, req));
      } catch (ServiceException se) {
        throw ProtobufUtil.getRemoteException(se);
      } finally {
        master.close();
      }
    }

    @Override
    public FTable getFTableDescriptor(byte[] tableName) throws IOException {
      if (tableName == null || tableName.length == 0)
        return null;

      MasterMonitorKeepAliveConnection master = getKeepAliveMasterMonitor();
      GetTableDescriptorsResponse htds;
      try {
        List<String> tables = new ArrayList<String>();
        tables.add(Bytes.toString(tableName));
        GetTableDescriptorsRequest req = RequestConverter
            .buildGetTableDescriptorsRequest(tables);
        htds = master.getTableDescriptors(null, req);
        if (htds.getTableSchemaList().size() > 0) {
          return ProtobufUtil.convertITableSchema(htds.getTableSchemaList()
              .get(0));
        }
        return null;
      } catch (ServiceException se) {
        throw ProtobufUtil.getRemoteException(se);
      } finally {
        master.close();
      }
    }

    @Override
    public EntityGroupLocation locateEntityGroup(byte[] tableName, byte[] row)
        throws IOException {
      return locateEntityGroup(tableName, row, true);
    }

    private EntityGroupLocation locateEntityGroup(final byte[] tableName,
        final byte[] row, boolean useCache) throws IOException {
      if (this.closed)
        throw new IOException(toString() + " closed");
      if (tableName == null || tableName.length == 0) {
        throw new IllegalArgumentException(
            "table name cannot be null or zero length");
      }
      ensureZookeeperTrackers();
      FTable table = TableSchemaCacheReader.getInstance(this.conf).getSchema(
          Bytes.toString(tableName));
      if (table.isRootTable()) {
        // entityGroup not in the cache - have to go to read the meta table
        return locateEntityGroupInMeta(tableName, row, useCache,
            entityGroupLock);
      } else {
        return locateEntityGroupInMeta(Bytes.toBytes(table.getParentName()),
            row, useCache, entityGroupLock);
      }
    }

    /**
     * Search the meta table for the EntityGroupLocation info that contains the
     * table and row we're seeking.
     */
    private EntityGroupLocation locateEntityGroupInMeta(byte[] tableName,
        byte[] row, boolean useCache, Object entityGroupLock)
        throws IOException {
      EntityGroupLocation location;
      // If we are supposed to be using the cache, look in the cache to see if
      // we already have the entityGroup.
      if (useCache) {
        location = getCachedLocation(tableName, row);
        if (location != null) {
          return location;
        }
      }

      for (int tries = 0; true; tries++) {
        if (tries >= numRetries) {
          throw new NoServerForEntityGroupException(
              "Unable to find entityGroup for " + Bytes.toStringBinary(row)
                  + " after " + numRetries + " tries.");
        }

        try {
          synchronized (entityGroupLock) {
            // we may want to pre-fetch some entityGroup info
            // into the global entityGroup cache for this table.
            if (getEntityGroupCachePrefetch(tableName)) {
              prefetchEntityGroupCache(tableName, row);
            }

            // Check the cache again for a hit in case some other thread made
            // the same query while we were waiting on the lock. If not supposed
            // to
            // be using the cache, delete any existing cached location so it
            // won't interfere.
            if (useCache) {
              location = getCachedLocation(tableName, row);
              if (location != null) {
                return location;
              }
            } else {
              deleteCachedLocation(tableName, row);
            }
          }

          // convert the row result into the EntityGroupLocation we need!
          location = FMetaReader.scanEntityGroupLocation(conf, tableName, row);
          if (location == null) {
            throw new NoServerForEntityGroupException(Bytes.toString(tableName));
          }

          EntityGroupInfo entityGroupInfo = location.getEntityGroupInfo();
          // possible we got a entityGroup of a different table...
          if (!Bytes.equals(entityGroupInfo.getTableName(), tableName)) {
            throw new TableNotFoundException("Table '"
                + Bytes.toString(tableName) + "' was not found, got: "
                + Bytes.toString(entityGroupInfo.getTableName()) + ".");
          }
          if (entityGroupInfo.isSplit()) {
            throw new EntityGroupOfflineException(
                "the only available entityGroup for"
                    + " the required row is a split parent,"
                    + " the daughters should be online soon: "
                    + entityGroupInfo.getEntityGroupNameAsString());
          }
          if (entityGroupInfo.isOffline()) {
            throw new EntityGroupOfflineException(
                "the entityGroup is offline, could"
                    + " be caused by a disable table call: "
                    + entityGroupInfo.getEntityGroupNameAsString());
          }

          String hostAndPort = location.getHostnamePort();
          if (hostAndPort.equals("")) {
            throw new NoServerForEntityGroupException(
                "No server address listed " + " for entityGroup "
                    + entityGroupInfo.getEntityGroupNameAsString()
                    + " containing row " + Bytes.toStringBinary(row));
          }

          // Instantiate the location
          cacheLocation(tableName, location);
          return location;
        } catch (TableNotFoundException e) {
          // if we got this error, probably means the table just plain doesn't
          // exist. rethrow the error immediately. this should always be coming
          // from the HTable constructor.
          throw e;
        } catch (IOException e) {
          if (e instanceof RemoteException) {
            e = ((RemoteException) e).unwrapRemoteException();
          }
          if (tries < numRetries - 1) {
            if (LOG.isDebugEnabled()) {
              LOG.debug("locateEntityGroupInMeta, attempt=" + tries + " of "
                  + this.numRetries + " failed; retrying after sleep of "
                  + ConnectionUtils.getPauseTime(this.pause, tries)
                  + " because: " + e.getMessage());
            }
          } else {
            throw e;
          }
        }

        try {
          Thread.sleep(ConnectionUtils.getPauseTime(this.pause, tries));
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          throw new IOException("Giving up trying to location entityGroup in "
              + "fmeta: thread is interrupted.");
        }
      }
    }

    /*
     * Search .FMETA. for the EntityGroupLocation info that contains the table
     * and row we're seeking. It will prefetch certain number of entityGroups
     * info and save them to the global entityGroup cache.
     */
    private void prefetchEntityGroupCache(final byte[] tableName,
        final byte[] row) {
      // Implement a new visitor for MetaScanner, and use it to walk through
      // the .FMETA.
      try {
        EntityGroupLocation locatoin = FMetaReader.scanEntityGroupLocation(
            conf, tableName, row);
        cacheLocation(tableName, locatoin);
      } catch (IOException e) {
        LOG.warn("Encountered problems when prefetch META table: ", e);
      }
    }

    @Override
    public void clearEntityGroupCache() {
      synchronized (this.cachedEntityGroupLocations) {
        this.cachedEntityGroupLocations.clear();
        this.cachedServers.clear();
      }
    }

    @Override
    public void clearEntityGroupCache(byte[] tableName) {
      synchronized (this.cachedEntityGroupLocations) {
        this.cachedEntityGroupLocations.remove(Bytes.mapKey(tableName));
      }
    }

    @Override
    public EntityGroupLocation relocateEntityGroup(byte[] tableName, byte[] row)
        throws IOException {
      return locateEntityGroup(tableName, row, false);
    }

    /**
     * Either the passed <code>isa</code> is null or <code>hostname</code> can
     * be but not both.
     *
     * @param hostname
     * @param port
     * @param protocolClass
     * @param version
     * @return Proxy.
     * @throws java.io.IOException
     */
    VersionedProtocol getProtocol(final String hostname, final int port,
        final Class<? extends VersionedProtocol> protocolClass,
        final long version) throws IOException {
      String rsName = Addressing.createHostAndPortStr(hostname, port);
      // See if we already have a connection (common case)
      Map<String, VersionedProtocol> protocols = this.servers.get(rsName);
      if (protocols == null) {
        protocols = new HashMap<String, VersionedProtocol>();
        Map<String, VersionedProtocol> existingProtocols = this.servers
            .putIfAbsent(rsName, protocols);
        if (existingProtocols != null) {
          protocols = existingProtocols;
        }
      }
      String protocol = protocolClass.getName();
      VersionedProtocol server = protocols.get(protocol);
      if (server == null) {
        // create a unique lock for this RS + protocol (if necessary)
        String lockKey = protocol + "@" + rsName;
        this.connectionLock.putIfAbsent(lockKey, lockKey);
        // get the RS lock
        synchronized (this.connectionLock.get(lockKey)) {
          // do one more lookup in case we were stalled above
          server = protocols.get(protocol);
          if (server == null) {
            try {
              // Only create isa when we need to.
              InetSocketAddress address = new InetSocketAddress(hostname, port);
              // definitely a cache miss. establish an RPC for this RS
              server = WaspRPC.waitForProxy(protocolClass, version, address,
                  this.conf, this.maxRPCAttempts, this.rpcTimeout,
                  this.rpcTimeout);
              protocols.put(protocol, server);
            } catch (RemoteException e) {
              LOG.warn("RemoteException connecting to RS", e);
              // Throw what the RemoteException was carrying.
              throw e.unwrapRemoteException();
            }
          }
        }
      }
      return server;
    }

    @Override
    public EntityGroupLocation getEntityGroupLocation(byte[] tableName,
        byte[] row, boolean reload) throws IOException {
      return reload ? relocateEntityGroup(tableName, row) : locateEntityGroup(
          tableName, row);
    }

    @Override
    public <T> T getFServerWithRetries(ServerCallable<T> callable)
        throws IOException, RuntimeException {
      return callable.withRetries();
    }

    @Override
    public <T> T getFServerWithoutRetries(ServerCallable<T> callable)
        throws IOException, RuntimeException {
      return callable.withoutRetries();
    }

    @Override
    public void setEntityGroupCachePrefetch(byte[] tableName, boolean enable) {
      if (!enable) {
        entityGroupCachePrefetchDisabledTables.add(Bytes.mapKey(tableName));
      } else {
        entityGroupCachePrefetchDisabledTables.remove(Bytes.mapKey(tableName));
      }
    }

    @Override
    public boolean getEntityGroupCachePrefetch(byte[] tableName) {
      return !entityGroupCachePrefetchDisabledTables.contains(Bytes
          .mapKey(tableName));
    }

    @Override
    public void prewarmEntityGroupCache(byte[] tableName,
        Map<EntityGroupInfo, ServerName> entityGroups) {
      for (Entry<EntityGroupInfo, ServerName> e : entityGroups.entrySet()) {
        ServerName sn = e.getValue();
        if (sn == null || sn.getHostAndPort() == null)
          continue;
        cacheLocation(tableName,
            new EntityGroupLocation(e.getKey(), sn.getHostname(), sn.getPort()));
      }
    }

    /*
     * @param tableName
     *
     * @return Map of cached locations for passed <code>tableName</code>
     */
    private SoftValueSortedMap<byte[], EntityGroupLocation> getTableLocations(
        final byte[] tableName) {
      // find the map of cached locations for this table
      Integer key = Bytes.mapKey(tableName);
      SoftValueSortedMap<byte[], EntityGroupLocation> result;
      synchronized (this.cachedEntityGroupLocations) {
        result = this.cachedEntityGroupLocations.get(key);
        // if tableLocations for this table isn't built yet, make one
        if (result == null) {
          result = new SoftValueSortedMap<byte[], EntityGroupLocation>(
              Bytes.BYTES_COMPARATOR);
          this.cachedEntityGroupLocations.put(key, result);
        }
      }
      return result;
    }

    @Override
    public FTable[] getFTableDescriptors(List<String> tableNames)
        throws IOException {
      if (tableNames == null || tableNames.isEmpty())
        return new FTable[0];
      MasterMonitorKeepAliveConnection master = getKeepAliveMasterMonitor();
      try {
        GetTableDescriptorsRequest req = RequestConverter
            .buildGetTableDescriptorsRequest(tableNames);
        return ProtobufUtil.getHTableDescriptorArray(master
            .getTableDescriptors(null, req));
      } catch (ServiceException se) {
        throw ProtobufUtil.getRemoteException(se);
      } finally {
        master.close();
      }
    }

    @Override
    public boolean isClosed() {
      return this.closed;
    }

    @Override
    public void clearCaches(String sn) {
      clearCachedLocationForServer(sn);
    }

    @Override
    public List<EntityGroupLocation> locateEntityGroups(byte[] tableName)
        throws IOException {
      return locateEntityGroupsInMeta(tableName, true, entityGroupLock);
    }

    @Override
    public int relocateEntityGroupsInCache(byte[] tableName) throws IOException {
      locateEntityGroupsInMeta(tableName, true, entityGroupLock);
      return getNumberOfCachedEntityGroupLocations(tableName);
    }

    private List<EntityGroupLocation> locateEntityGroupsInMeta(
        byte[] tableName, boolean useCache, Object entityGroupLock)
        throws IOException {
      List<EntityGroupLocation> locations;
      List<EntityGroupLocation> realLocations = new ArrayList<EntityGroupLocation>();
      final byte[] rootTableName = FMetaReader.getRootTable(conf, tableName);
      // If we are supposed to be using the cache, look in the cache to see if
      // we already have the entityGroup.

      // build the key of the meta entityGroup we should be looking for.
      // the extra 9's on the end are necessary to allow "exact" matches
      // without knowing the precise entityGroup names.
      for (int tries = 0; true; tries++) {
        if (tries >= numRetries) {
          throw new NoServerForEntityGroupException(
              "Unable to find entityGroups for table "
                  + Bytes.toStringBinary(tableName) + " after " + numRetries
                  + " tries.");
        }

        try {

          // convert the row result into the EntityGroupLocation we need!
          locations = FMetaReader.getEntityGroupLocations(conf, tableName);
          if (locations == null || locations.size() == 0) {
            throw new NoServerForEntityGroupException(Bytes.toString(tableName));
          }

          for (EntityGroupLocation location : locations) {

            EntityGroupInfo entityGroupInfo = location.getEntityGroupInfo();
            // possible we got a entityGroup of a different table...
            if (!Bytes.equals(entityGroupInfo.getTableName(), rootTableName)) {
              throw new TableNotFoundException("Table '"
                  + Bytes.toString(tableName) + "' was not found, got: "
                  + Bytes.toString(entityGroupInfo.getTableName()) + ".");
            }
            if (entityGroupInfo.isSplit()) {
              throw new EntityGroupOfflineException(
                  "the only available entityGroup for"
                      + " the required row is a split parent,"
                      + " the daughters should be online soon: "
                      + entityGroupInfo.getEntityGroupNameAsString());
            }
            if (entityGroupInfo.isOffline()) {
              throw new EntityGroupOfflineException(
                  "the entityGroup is offline, could"
                      + " be caused by a disable table call: "
                      + entityGroupInfo.getEntityGroupNameAsString());
            }

            String hostAndPort = location.getHostnamePort();
            if (hostAndPort.equals("")) {
              throw new NoServerForEntityGroupException(
                  "No server address listed " + " for entityGroup "
                      + entityGroupInfo.getEntityGroupNameAsString()
                      + " containing row "
                      + Bytes.toStringBinary(entityGroupInfo.getStartKey()));
            }

            // Instantiate the location
            cacheLocation(tableName, location);
            realLocations.add(location);
          }
          return realLocations;
        } catch (TableNotFoundException e) {
          // if we got this error, probably means the table just plain doesn't
          // exist. rethrow the error immediately. this should always be coming
          // from the HTable constructor.
          throw e;
        } catch (IOException e) {
          if (e instanceof RemoteException) {
            e = ((RemoteException) e).unwrapRemoteException();
          }
          if (tries < numRetries - 1) {
            if (LOG.isDebugEnabled()) {
              LOG.debug("locateEntityGroupInMeta, attempt=" + tries + " of "
                  + this.numRetries + " failed; retrying after sleep of "
                  + ConnectionUtils.getPauseTime(this.pause, tries)
                  + " because: " + e.getMessage());
            }
          } else {
            throw e;
          }
        }

        try {
          Thread.sleep(ConnectionUtils.getPauseTime(this.pause, tries));
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          throw new IOException("Giving up trying to location entityGroup in "
              + "fmeta: thread is interrupted.");
        }
      }
    }

    /**
     * Put a newly discovered EntityGroupLocation into the cache.
     */
    private void cacheLocation(final byte[] tableName,
        final EntityGroupLocation location) {
      byte[] startKey = location.getEntityGroupInfo().getStartKey();
      Map<byte[], EntityGroupLocation> tableLocations = getTableLocations(tableName);
      boolean hasNewCache = false;
      synchronized (this.cachedEntityGroupLocations) {
        cachedServers.add(location.getHostnamePort());
        hasNewCache = (tableLocations.put(startKey, location) == null);
      }
      if (hasNewCache) {
        LOG.debug("Cached location for "
            + location.getEntityGroupInfo().getEntityGroupNameAsString()
            + " is " + location.getHostnamePort());
      }
    }

    /*
     * Return the number of cached entityGroup for a table. It will only be
     * called from a unit test.
     */
    int getNumberOfCachedEntityGroupLocations(final byte[] tableName) {
      Integer key = Bytes.mapKey(tableName);
      synchronized (this.cachedEntityGroupLocations) {
        Map<byte[], EntityGroupLocation> tableLocs = this.cachedEntityGroupLocations
            .get(key);

        if (tableLocs == null) {
          return 0;
        }
        return tableLocs.values().size();
      }
    }

    /**
     * Check the entityGroup cache to see whether a entityGroup is cached yet or
     * not. Called by unit tests.
     *
     * @param tableName
     *          tableName
     * @param row
     *          row
     * @return entityGroup cached or not.
     */
    boolean isEntityGroupCached(final byte[] tableName, final byte[] row) {
      EntityGroupLocation location = getCachedLocation(tableName, row);
      return location != null;
    }

    /*
     * Search the cache for a location that fits our table and row key. Return
     * null if no suitable entityGroup is located.
     *
     * @param tableName
     *
     * @param row
     *
     * @return Null or entityGroup location found in cache.
     */
    EntityGroupLocation getCachedLocation(final byte[] tableName,
        final byte[] row) {
      SoftValueSortedMap<byte[], EntityGroupLocation> tableLocations = getTableLocations(tableName);

      // start to examine the cache. we can only do cache actions
      // if there's something in the cache for this table.
      if (tableLocations.isEmpty()) {
        return null;
      }

      EntityGroupLocation possibleEntityGroup = tableLocations.get(row);
      if (possibleEntityGroup != null) {
        return possibleEntityGroup;
      }

      possibleEntityGroup = tableLocations.lowerValueByKey(row);
      if (possibleEntityGroup == null) {
        return null;
      }

      // make sure that the end key is greater than the row we're looking
      // for, otherwise the row actually belongs in the next entityGroup, not
      // this one. the exception case is when the end key is
      // FConstants.EMPTY_END_ROW, signifying that the entityGroup we're
      // checking is actually the last entityGroup in the table.
      byte[] endKey = possibleEntityGroup.getEntityGroupInfo().getEndKey();
      if (Bytes.equals(endKey, FConstants.EMPTY_END_ROW)||
          KeyValue.getRowComparator(tableName).compareRows(
              endKey, 0, endKey.length, row, 0, row.length) > 0) {
        return possibleEntityGroup;
      }

      // Passed all the way through, so we got nothing - complete cache miss
      return null;
    }

    /**
     * Delete a cached location
     *
     * @param tableName
     *          tableName
     * @param row
     */
    void deleteCachedLocation(final byte[] tableName, final byte[] row) {
      synchronized (this.cachedEntityGroupLocations) {
        Map<byte[], EntityGroupLocation> tableLocations = getTableLocations(tableName);
        // start to examine the cache. we can only do cache actions
        // if there's something in the cache for this table.
        if (!tableLocations.isEmpty()) {
          EntityGroupLocation egl = getCachedLocation(tableName, row);
          if (egl != null) {
            tableLocations.remove(egl.getEntityGroupInfo().getStartKey());
            if (LOG.isDebugEnabled()) {
              LOG.debug("Removed "
                  + egl.getEntityGroupInfo().getEntityGroupNameAsString()
                  + " for tableName=" + Bytes.toString(tableName)
                  + " from cache " + "because of " + Bytes.toStringBinary(row));
            }
          }
        }
      }
    }

    /*
     * Delete all cached entries of a table that maps to a specific location.
     *
     * @param tablename
     *
     * @param server
     */
    private void clearCachedLocationForServer(final String server) {
      boolean deletedSomething = false;
      synchronized (this.cachedEntityGroupLocations) {
        if (!cachedServers.contains(server)) {
          return;
        }
        for (Map<byte[], EntityGroupLocation> tableLocations : cachedEntityGroupLocations
            .values()) {
          for (Entry<byte[], EntityGroupLocation> e : tableLocations
              .entrySet()) {
            if (e.getValue().getHostnamePort().equals(server)) {
              tableLocations.remove(e.getKey());
              deletedSomething = true;
            }
          }
        }
        cachedServers.remove(server);
      }
      if (deletedSomething && LOG.isDebugEnabled()) {
        LOG.debug("Removed all cached entityGroup locations that map to "
            + server);
      }
    }

    @Override
    public void abort(String msg, Throwable t) {
      if (t instanceof KeeperException) {
        LOG.info("This client just lost it's session with ZooKeeper, will"
            + " automatically reconnect when needed.");
        if (t instanceof KeeperException.SessionExpiredException) {
          LOG.info("ZK session expired. This disconnect could have been"
              + " caused by a network partition or a long-running GC pause,"
              + " either way it's recommended that you verify your environment.");
          resetZooKeeperTrackers();
        }
        return;
      }
      if (t != null)
        LOG.fatal(msg, t);
      else
        LOG.fatal(msg);
      this.aborted = true;
      close();

    }

    @Override
    public boolean isAborted() {
      return this.aborted;
    }

    /**
     * @return the refCount
     */
    public int getRefCount() {
      return refCount;
    }

    public void stopProxyOnClose(boolean stopProxy) {
      this.stopProxy = stopProxy;
    }

    @Override
    public AdminProtocol getAdmin(final String hostname, final int port)
        throws IOException {
      return (AdminProtocol) getProtocol(hostname, port, adminClass,
          AdminProtocol.VERSION);
    }

    @Override
    public ClientProtocol getClient(final String hostname, final int port)
        throws IOException {
      return (ClientProtocol) getProtocol(hostname, port, clientClass,
          ClientProtocol.VERSION);
    }

    private static class MasterProtocolState {
      public MasterProtocol protocol;
      public int userCount;
      public long keepAliveUntil = Long.MAX_VALUE;
      public final Class<? extends MasterProtocol> protocolClass;
      public long version;

      public MasterProtocolState(
          final Class<? extends MasterProtocol> protocolClass, long version) {
        this.protocolClass = protocolClass;
        this.version = version;
      }
    }

    private boolean isKeepAliveMasterConnectedAndRunning(
        MasterProtocolState protocolState) {
      if (protocolState.protocol == null) {
        return false;
      }
      try {
        return protocolState.protocol.isMasterRunning(null,
            RequestConverter.buildIsMasterRunningRequest())
            .getIsMasterRunning();
      } catch (UndeclaredThrowableException e) {
        // It's somehow messy, but we can receive exceptions such as
        // java.net.ConnectException but they're not declared. So we catch
        // it...
        LOG.info("Master connection is not running anymore",
            e.getUndeclaredThrowable());
        return false;
      } catch (ServiceException se) {
        LOG.warn("Checking master connection", se);
        return false;
      }
    }

    private ZooKeeperKeepAliveConnection keepAliveZookeeper;
    private int keepAliveZookeeperUserCount;
    private boolean canCloseZKW = true;
    private long keepZooKeeperWatcherAliveUntil = Long.MAX_VALUE;

    /**
     * Retrieve a shared ZooKeeperWatcher. You must close it it once you've have
     * finished with it.
     *
     * @return The shared instance. Never returns null.
     */
    public ZooKeeperKeepAliveConnection getKeepAliveZooKeeperWatcher()
        throws IOException {
      synchronized (masterAndZKLock) {

        if (keepAliveZookeeper == null) {
          // We don't check that our link to ZooKeeper is still valid
          // But there is a retry mechanism in the ZooKeeperWatcher itself
          keepAliveZookeeper = new ZooKeeperKeepAliveConnection(conf,
              this.toString(), this);
        }
        keepAliveZookeeperUserCount++;
        keepZooKeeperWatcherAliveUntil = Long.MAX_VALUE;

        return keepAliveZookeeper;
      }
    }

    /**
     * Create a new Master proxy. Try once only.
     */
    private MasterProtocol createMasterInterface(
        MasterProtocolState masterProtocolState) throws IOException,
        KeeperException, ServiceException {

      ZooKeeperKeepAliveConnection zkw;
      try {
        zkw = getKeepAliveZooKeeperWatcher();
      } catch (IOException e) {
        throw new ZooKeeperConnectionException("Can't connect to ZooKeeper", e);
      }

      try {
        checkIfBaseNodeAvailable(zkw);
        ServerName sn = MasterAddressTracker.getMasterAddress(zkw);
        if (sn == null) {
          String msg = "ZooKeeper available but no active master location found";
          LOG.info(msg);
          throw new MasterNotRunningException(msg);
        }

        InetSocketAddress isa = new InetSocketAddress(sn.getHostname(),
            sn.getPort());
        MasterProtocol tryMaster = (MasterProtocol) WaspRPC.getProxy(
            masterProtocolState.protocolClass, masterProtocolState.version,
            isa, this.conf, this.rpcTimeout);

        if (tryMaster.isMasterRunning(null,
            RequestConverter.buildIsMasterRunningRequest())
            .getIsMasterRunning()) {
          return tryMaster;
        } else {
          WaspRPC.stopProxy(tryMaster);
          String msg = "Can create a proxy to master, but it is not running";
          LOG.info(msg);
          throw new MasterNotRunningException(msg);
        }
      } finally {
        zkw.close();
      }
    }

    /**
     * Create a master, retries if necessary.
     */
    private MasterProtocol createMasterWithRetries(
        MasterProtocolState masterProtocolState)
        throws MasterNotRunningException {

      // The lock must be at the beginning to prevent multiple master creation
      // (and leaks) in a multithread context
      synchronized (this.masterAndZKLock) {
        Exception exceptionCaught = null;
        MasterProtocol master = null;
        int tries = 0;
        while (!this.closed && master == null) {
          tries++;
          try {
            master = createMasterInterface(masterProtocolState);
          } catch (IOException e) {
            exceptionCaught = e;
          } catch (KeeperException e) {
            exceptionCaught = e;
          } catch (ServiceException e) {
            exceptionCaught = e;
          }

          if (exceptionCaught != null)
            // It failed. If it's not the last try, we're going to wait a little
            if (tries < numRetries) {
              long pauseTime = ConnectionUtils.getPauseTime(this.pause, tries);
              LOG.info("getMaster attempt " + tries + " of " + numRetries
                  + " failed; retrying after sleep of " + pauseTime,
                  exceptionCaught);

              try {
                Thread.sleep(pauseTime);
              } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(
                    "Thread was interrupted while trying to connect to master.",
                    e);
              }

            } else {
              // Enough tries, we stop now
              LOG.info("getMaster attempt " + tries + " of " + numRetries
                  + " failed; no more retrying.", exceptionCaught);
              throw new MasterNotRunningException(exceptionCaught);
            }
        }

        if (master == null) {
          // implies this.closed true
          throw new MasterNotRunningException(
              "Connection was closed while trying to get master");
        }

        return master;
      }
    }

    private void checkIfBaseNodeAvailable(ZooKeeperWatcher zkw)
        throws MasterNotRunningException {
      String errorMsg;
      try {
        if (ZKUtil.checkExists(zkw, zkw.baseZNode) == -1) {
          errorMsg = "The node "
              + zkw.baseZNode
              + " is not in ZooKeeper. "
              + "It should have been written by the master. "
              + "Check the value configured in 'zookeeper.znode.parent'. "
              + "There could be a mismatch with the one configured in the master.";
          LOG.error(errorMsg);
          throw new MasterNotRunningException(errorMsg);
        }
      } catch (KeeperException e) {
        errorMsg = "Can't get connection to ZooKeeper: " + e.getMessage();
        LOG.error(errorMsg);
        throw new MasterNotRunningException(errorMsg, e);
      }
    }

    private static class MasterProtocolHandler implements InvocationHandler {
      private FConnectionImplementation connection;
      private MasterProtocolState protocolStateTracker;

      protected MasterProtocolHandler(FConnectionImplementation connection,
          MasterProtocolState protocolStateTracker) {
        this.connection = connection;
        this.protocolStateTracker = protocolStateTracker;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        if (method.getName().equals("close")
            && method.getParameterTypes().length == 0) {
          release(connection, protocolStateTracker);
          return null;
        } else {
          try {
            return method.invoke(protocolStateTracker.protocol, args);
          } catch (InvocationTargetException e) {
            // We will have this for all the exception, checked on not, sent
            // by any layer, including the functional exception
            Throwable cause = e.getCause();
            if (cause == null) {
              throw new RuntimeException(
                  "Proxy invocation failed and getCause is null", e);
            }
            if (cause instanceof UndeclaredThrowableException) {
              cause = cause.getCause();
            }
            throw cause;
          }
        }
      }

      private void release(FConnectionImplementation connection,
          MasterProtocolState target) {
        connection.releaseMaster(target);
      }
    }

    private void releaseMaster(MasterProtocolState protocolState) {
      if (protocolState.protocol == null) {
        return;
      }
      synchronized (masterAndZKLock) {
        --protocolState.userCount;
        if (protocolState.userCount <= 0) {
          protocolState.keepAliveUntil = System.currentTimeMillis() + keepAlive;
        }
      }
    }

    void releaseZooKeeperWatcher(ZooKeeperWatcher zkw) {
      if (zkw == null) {
        return;
      }
      synchronized (masterAndZKLock) {
        --keepAliveZookeeperUserCount;
        if (keepAliveZookeeperUserCount <= 0) {
          keepZooKeeperWatcherAliveUntil = System.currentTimeMillis()
              + keepAlive;
        }
      }
    }

    /**
     * This function allows HBaseAdmin and potentially others to get a shared
     * master connection.
     *
     * @return The shared instance. Never returns null.
     * @throws com.alibaba.wasp.MasterNotRunningException
     */
    private Object getKeepAliveMasterProtocol(
        MasterProtocolState protocolState, Class connectionClass)
        throws MasterNotRunningException {
      synchronized (masterAndZKLock) {
        if (!isKeepAliveMasterConnectedAndRunning(protocolState)) {
          if (protocolState.protocol != null) {
            WaspRPC.stopProxy(protocolState.protocol);
          }
          protocolState.protocol = null;
          protocolState.protocol = createMasterWithRetries(protocolState);
        }
        protocolState.userCount++;
        protocolState.keepAliveUntil = Long.MAX_VALUE;

        return Proxy.newProxyInstance(connectionClass.getClassLoader(),
            new Class[] { connectionClass }, new MasterProtocolHandler(this,
                protocolState));
      }
    }

    MasterProtocolState masterAdminProtocol = new MasterProtocolState(
        FMasterAdminProtocol.class, FMasterAdminProtocol.VERSION);
    MasterProtocolState masterMonitorProtocol = new MasterProtocolState(
        FMasterMonitorProtocol.class, FMasterMonitorProtocol.VERSION);

    /**
     *
     * @throws com.alibaba.wasp.MasterNotRunningException
     * @see com.alibaba.wasp.client.FConnection#getKeepAliveMasterAdmin()
     */
    @Override
    public MasterAdminKeepAliveConnection getKeepAliveMasterAdmin()
        throws MasterNotRunningException {
      return (MasterAdminKeepAliveConnection) getKeepAliveMasterProtocol(
          masterAdminProtocol, MasterAdminKeepAliveConnection.class);
    }

    /**
     *
     * @throws com.alibaba.wasp.MasterNotRunningException
     * @see com.alibaba.wasp.client.FConnection#getKeepAliveMasterMonitor()
     */
    @Override
    public MasterMonitorKeepAliveConnection getKeepAliveMasterMonitor()
        throws MasterNotRunningException {
      return (MasterMonitorKeepAliveConnection) getKeepAliveMasterProtocol(
          masterMonitorProtocol, MasterMonitorKeepAliveConnection.class);
    }

    /**
     *
     * @throws com.alibaba.wasp.MasterNotRunningException
     * @see com.alibaba.wasp.client.FConnection#getKeepAliveMasterMetaServer()
     */
    @Override
    public MetaServerKeepAliveConnection getKeepAliveMasterMetaServer()
        throws MasterNotRunningException {
      return (MetaServerKeepAliveConnection) getKeepAliveMasterProtocol(
          masterMonitorProtocol, MetaServerKeepAliveConnection.class);
    }

    /**
     * @see com.alibaba.wasp.client.FConnection#getMasterAdmin()
     */
    @Override
    public FMasterAdminProtocol getMasterAdmin() throws IOException {
      return getKeepAliveMasterAdmin();
    }

    /**
     * @see com.alibaba.wasp.client.FConnection#getMasterMonitor()
     */
    @Override
    public FMasterMonitorProtocol getMasterMonitor() throws IOException {
      return getKeepAliveMasterMonitor();
    }

    /**
     * @see com.alibaba.wasp.client.FConnection#getMetaServer()
     */
    @Override
    public FMetaServerProtocol getMetaServer() throws IOException {
      return getKeepAliveMasterMetaServer();
    }

    private void closeZooKeeperWatcher() {
      synchronized (masterAndZKLock) {
        if (keepAliveZookeeper != null) {
          LOG.info("Closing zookeeper sessionid=0x"
              + Long.toHexString(keepAliveZookeeper.getRecoverableZooKeeper()
                  .getSessionId()));
          keepAliveZookeeper.internalClose();
          keepAliveZookeeper = null;
        }
        keepAliveZookeeperUserCount = 0;
      }
    }

    private void closeMasterProtocol(MasterProtocolState protocolState) {
      if (protocolState.protocol != null) {
        LOG.info("Closing master protocol: "
            + protocolState.protocolClass.getName());
        WaspRPC.stopProxy(protocolState.protocol);
        protocolState.protocol = null;
      }
      protocolState.userCount = 0;
    }

    void close(boolean stopProxy) {
      if (this.closed) {
        return;
      }
      delayedClosing.stop("Closing connection");
      if (stopProxy) {
        closeMaster();
        for (Map<String, VersionedProtocol> i : servers.values()) {
          for (VersionedProtocol server : i.values()) {
            WaspRPC.stopProxy(server);
          }
        }
      }
      closeZooKeeperWatcher();
      this.servers.clear();
      this.closed = true;
    }

    @Override
    public void close() {
      if (managed) {
        FConnectionManager.deleteConnection(this, stopProxy, false);
      } else {
        close(true);
      }
      if (LOG.isTraceEnabled()) {
        LOG.debug("" + this.zooKeeper + " closed.");
      }
    }

    private void closeMaster() {
      synchronized (masterAndZKLock) {
        closeMasterProtocol(masterAdminProtocol);
        closeMasterProtocol(masterMonitorProtocol);
      }
    }

    /**
     * Creates a Chore thread to check the connections to master & zookeeper and
     * close them when they reach their closing time (
     * {@link MasterProtocolState} and {@link #keepZooKeeperWatcherAliveUntil}
     * ). Keep alive time is managed by the release functions and the variable
     * {@link #keepAlive}
     */
    private static class DelayedClosing extends Chore implements Stoppable {
      private FConnectionImplementation hci;
      Stoppable stoppable;

      private DelayedClosing(FConnectionImplementation hci, Stoppable stoppable) {
        super("ZooKeeperWatcher and Master delayed closing for connection "
            + hci, 60 * 1000, // We check every minutes
            stoppable);
        this.hci = hci;
        this.stoppable = stoppable;
      }

      static DelayedClosing createAndStart(FConnectionImplementation hci) {
        Stoppable stoppable = new Stoppable() {
          private volatile boolean isStopped = false;

          @Override
          public void stop(String why) {
            isStopped = true;
          }

          @Override
          public boolean isStopped() {
            return isStopped;
          }
        };

        return new DelayedClosing(hci, stoppable);
      }

      protected void closeMasterProtocol(MasterProtocolState protocolState) {
        if (System.currentTimeMillis() > protocolState.keepAliveUntil) {
          hci.closeMasterProtocol(protocolState);
          protocolState.keepAliveUntil = Long.MAX_VALUE;
        }
      }

      @Override
      protected void chore() {
        synchronized (hci.masterAndZKLock) {
          if (hci.canCloseZKW) {
            if (System.currentTimeMillis() > hci.keepZooKeeperWatcherAliveUntil) {

              hci.closeZooKeeperWatcher();
              hci.keepZooKeeperWatcherAliveUntil = Long.MAX_VALUE;
            }
          }
          closeMasterProtocol(hci.masterAdminProtocol);
          closeMasterProtocol(hci.masterMonitorProtocol);
        }
      }

      @Override
      public void stop(String why) {
        stoppable.stop(why);
      }

      @Override
      public boolean isStopped() {
        return stoppable.isStopped();
      }
    }
  }

  /**
   * Denotes a unique key to a {@link FConnection} instance.
   *
   * In essence, this class captures the properties in
   * {@link org.apache.hadoop.conf.Configuration} that may be used in the
   * process of establishing a connection. In light of that, if any new such
   * properties are introduced into the mix, they must be added to the
   * {@link FConnectionKey#properties} list.
   *
   */
  static class FConnectionKey {
    public static String[] CONNECTION_PROPERTIES = new String[] {
        FConstants.ZOOKEEPER_QUORUM, FConstants.ZOOKEEPER_ZNODE_PARENT,
        FConstants.ZOOKEEPER_CLIENT_PORT,
        FConstants.ZOOKEEPER_RECOVERABLE_WAITTIME,
        FConstants.WASP_CLIENT_RETRIES_NUMBER,
        FConstants.WASP_CLIENT_RPC_MAXATTEMPTS,
        FConstants.WASP_RPC_TIMEOUT_KEY, };

    private Map<String, String> properties;
    private String username;

    public FConnectionKey(Configuration conf) {
      Map<String, String> m = new HashMap<String, String>();
      if (conf != null) {
        for (String property : CONNECTION_PROPERTIES) {
          String value = conf.get(property);
          if (value != null) {
            m.put(property, value);
          }
        }
      }
      this.properties = Collections.unmodifiableMap(m);
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      if (username != null) {
        result = username.hashCode();
      }
      for (String property : CONNECTION_PROPERTIES) {
        String value = properties.get(property);
        if (value != null) {
          result = prime * result + value.hashCode();
        }
      }

      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      FConnectionKey that = (FConnectionKey) obj;
      if (this.username != null && !this.username.equals(that.username)) {
        return false;
      } else if (this.username == null && that.username != null) {
        return false;
      }
      if (this.properties == null) {
        if (that.properties != null) {
          return false;
        }
      } else {
        if (that.properties == null) {
          return false;
        }
        for (String property : CONNECTION_PROPERTIES) {
          String thisValue = this.properties.get(property);
          String thatValue = that.properties.get(property);
          if (thisValue == thatValue) {
            continue;
          }
          if (thisValue == null || !thisValue.equals(thatValue)) {
            return false;
          }
        }
      }
      return true;
    }

    @Override
    public String toString() {
      return "FConnectionKey{" + "properties=" + properties + ", username='"
          + username + '\'' + '}';
    }
  }

  private static void deleteConnection(FConnection connection,
      boolean stopProxy, boolean staleConnection) {
    synchronized (WASP_INSTANCES) {
      for (Entry<FConnectionKey, FConnectionImplementation> connectionEntry : WASP_INSTANCES
          .entrySet()) {
        if (connectionEntry.getValue() == connection) {
          deleteConnection(connectionEntry.getKey(), stopProxy, staleConnection);
          break;
        }
      }
    }
  }

  private static void deleteConnection(FConnectionKey connectionKey,
      boolean stopProxy, boolean staleConnection) {
    synchronized (WASP_INSTANCES) {
      FConnectionImplementation connection = WASP_INSTANCES.get(connectionKey);
      if (connection != null) {
        connection.decCount();
        if (connection.isZeroReference() || staleConnection) {
          WASP_INSTANCES.remove(connectionKey);
          connection.close(stopProxy);
        } else if (stopProxy) {
          connection.stopProxyOnClose(stopProxy);
        }
      } else {
        LOG.error("Connection not found in the list, can't delete it "
            + "(connection key=" + connectionKey
            + "). May be the key was modified?");
      }
    }
  }

  /**
   * Delete connection information for the instance specified by configuration.
   * If there are no more references to it, this will then close connection to
   * the zookeeper ensemble and let go of all resources.
   *
   * @param conf
   *          configuration whose identity is used to find {@link FConnection}
   *          instance.
   * @param stopProxy
   *          Shuts down all the proxy's put up to cluster members including to
   *          cluster FMaster. Calls
   *          {@link com.alibaba.wasp.ipc.WaspRPC#stopProxy(com.alibaba.wasp.ipc.VersionedProtocol)} .
   */
  public static void deleteConnection(Configuration conf, boolean stopProxy) {
    deleteConnection(new FConnectionKey(conf), stopProxy, false);
  }

  /**
   * Delete information for all connections.
   *
   * @param stopProxy
   *          stop the proxy as well
   * @throws java.io.IOException
   */
  public static void deleteAllConnections(boolean stopProxy) {
    synchronized (WASP_INSTANCES) {
      Set<FConnectionKey> connectionKeys = new HashSet<FConnectionKey>();
      connectionKeys.addAll(WASP_INSTANCES.keySet());
      for (FConnectionKey connectionKey : connectionKeys) {
        deleteConnection(connectionKey, stopProxy, false);
      }
      WASP_INSTANCES.clear();
    }
  }

  /**
   * Set the number of retries to use serverside when trying to communicate with
   * another server over {@link com.alibaba.wasp.client.FConnection}. Used
   * updating catalog tables, etc. Call this method before we create any
   * Connections.
   *
   * @param c
   *          The Configuration instance to set the retries into.
   * @param log
   *          Used to log what we set in here.
   */
  public static void setServerSideFConnectionRetries(final Configuration c,
      final Log log) {
    int fcRetries = c.getInt(FConstants.WASP_CLIENT_RETRIES_NUMBER,
        FConstants.DEFAULT_WASP_CLIENT_RETRIES_NUMBER);
    // Go big. Multiply by 10. If we can't get to meta after this many retries
    // then something seriously wrong.
    int serversideMultiplier = c.getInt(
        "wasp.client.serverside.retries.multiplier", 10);
    int retries = fcRetries * serversideMultiplier;
    c.setInt(FConstants.WASP_CLIENT_RETRIES_NUMBER, retries);
    log.debug("Set serverside FConnection retries=" + retries);
  }
}
TOP

Related Classes of com.alibaba.wasp.client.FConnectionManager$FConnectionKey

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.