com.alibaba.wasp.client.FConnectionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.wasp.client.FConnectionManager.java

Source

/**
 * 
 * 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);
    }
}