You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ji...@apache.org on 2008/07/30 03:45:45 UTC

svn commit: r680902 [2/2] - in /hadoop/hbase/trunk: CHANGES.txt conf/hbase-default.xml src/java/org/apache/hadoop/hbase/HConstants.java src/java/org/apache/hadoop/hbase/client/HBaseAdmin.java src/java/org/apache/hadoop/hbase/client/HConnectionManager.java

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/client/HConnectionManager.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/client/HConnectionManager.java?rev=680902&r1=680901&r2=680902&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/client/HConnectionManager.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/client/HConnectionManager.java Tue Jul 29 18:45:44 2008
@@ -1,901 +1,908 @@
-/**
- * Copyright 2007 The Apache Software Foundation
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.hadoop.hbase.client;
-
-import java.io.IOException;
-import java.lang.reflect.UndeclaredThrowableException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.hadoop.hbase.DoNotRetryIOException;
-import org.apache.hadoop.hbase.HBaseConfiguration;
-import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.HRegionInfo;
-import org.apache.hadoop.hbase.HRegionLocation;
-import org.apache.hadoop.hbase.HServerAddress;
-import org.apache.hadoop.hbase.HTableDescriptor;
-import org.apache.hadoop.hbase.LocalHBaseCluster;
-import org.apache.hadoop.hbase.MasterNotRunningException;
-import org.apache.hadoop.hbase.RemoteExceptionHandler;
-import org.apache.hadoop.hbase.TableNotFoundException;
-import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
-import org.apache.hadoop.hbase.io.Cell;
-import org.apache.hadoop.hbase.io.RowResult;
-import org.apache.hadoop.hbase.ipc.HMasterInterface;
-import org.apache.hadoop.hbase.ipc.HRegionInterface;
-import org.apache.hadoop.hbase.ipc.HbaseRPC;
-import org.apache.hadoop.hbase.util.Bytes;
-import org.apache.hadoop.hbase.util.SoftSortedMap;
-import org.apache.hadoop.hbase.util.Writables;
-import org.apache.hadoop.ipc.RemoteException;
-
-/**
- * A non-instantiable class that manages connections to multiple tables in
- * multiple HBase instances.
- * 
- * Used by {@link HTable} and {@link HBaseAdmin}
- */
-public class HConnectionManager implements HConstants {
-  /*
-   * Private. Not instantiable.
-   */
-  private HConnectionManager() {
-    super();
-  }
-  
-  // A Map of master HServerAddress -> connection information for that instance
-  // Note that although the Map is synchronized, the objects it contains
-  // are mutable and hence require synchronized access to them
-  private static final Map<String, TableServers> HBASE_INSTANCES =
-    new ConcurrentHashMap<String, TableServers>();
-
-  /**
-   * Get the connection object for the instance specified by the configuration
-   * If no current connection exists, create a new connection for that instance
-   * @param conf
-   * @return HConnection object for the instance specified by the configuration
-   */
-  public static HConnection getConnection(HBaseConfiguration conf) {
-    TableServers connection;
-    synchronized (HBASE_INSTANCES) {
-      String instanceName = conf.get(HBASE_DIR);
-      connection = HBASE_INSTANCES.get(instanceName);
-      if (connection == null) {
-        connection = new TableServers(conf);
-        HBASE_INSTANCES.put(instanceName, connection);
-      }
-    }
-    return connection;
-  }
-  
-  /**
-   * Delete connection information for the instance specified by the configuration
-   * @param conf
-   */
-  public static void deleteConnectionInfo(HBaseConfiguration conf) {
-    synchronized (HBASE_INSTANCES) {
-      HBASE_INSTANCES.remove(conf.get(HBASE_DIR));
-    }
-  }
-
-  /**
-   * Clear the static map of connection info.
-   */
-  public static void deleteConnectionInfo() {
-    synchronized (HBASE_INSTANCES) {
-      HBASE_INSTANCES.clear();
-    }
-  }
-
-  
-  /* Encapsulates finding the servers for an HBase instance */
-  private static class TableServers implements HConnection, HConstants {
-    private static final Log LOG = LogFactory.getLog(TableServers.class);
-    private final Class<? extends HRegionInterface> serverInterfaceClass;
-    private final long pause;
-    private final int numRetries;
-    private final int maxRPCAttempts;
-
-    private final Integer masterLock = new Integer(0);
-    private volatile boolean closed;
-    private volatile HMasterInterface master;
-    private volatile boolean masterChecked;
-    
-    private final Integer rootRegionLock = new Integer(0);
-    private final Integer metaRegionLock = new Integer(0);
-    private final Integer userRegionLock = new Integer(0);
-        
-    private volatile HBaseConfiguration conf;
-    
-    // Known region HServerAddress.toString() -> HRegionInterface 
-    private final Map<String, HRegionInterface> servers =
-      new ConcurrentHashMap<String, HRegionInterface>();
-
-    private HRegionLocation rootRegionLocation; 
-    
-    private final Map<Integer, SoftSortedMap<byte [], HRegionLocation>> 
-      cachedRegionLocations = Collections.synchronizedMap(
-         new HashMap<Integer, SoftSortedMap<byte [], HRegionLocation>>());
-    
-    /** 
-     * constructor
-     * @param conf Configuration object
-     */
-    @SuppressWarnings("unchecked")
-    public TableServers(HBaseConfiguration conf) {
-      this.conf = LocalHBaseCluster.doLocal(new HBaseConfiguration(conf));
-      
-      String serverClassName =
-        conf.get(REGION_SERVER_CLASS, DEFAULT_REGION_SERVER_CLASS);
-
-      this.closed = false;
-      
-      try {
-        this.serverInterfaceClass =
-          (Class<? extends HRegionInterface>) Class.forName(serverClassName);
-        
-      } catch (ClassNotFoundException e) {
-        throw new UnsupportedOperationException(
-            "Unable to find region server interface " + serverClassName, e);
-      }
-
-      this.pause = conf.getLong("hbase.client.pause", 30 * 1000);
-      this.numRetries = conf.getInt("hbase.client.retries.number", 5);
-      this.maxRPCAttempts = conf.getInt("hbase.client.rpc.maxattempts", 1);
-      
-      this.master = null;
-      this.masterChecked = false;
-    }
-    
-    /** {@inheritDoc} */
-    public HMasterInterface getMaster() throws MasterNotRunningException {
-      HServerAddress masterLocation = null;
-      synchronized (this.masterLock) {
-        for (int tries = 0;
-          !this.closed &&
-          !this.masterChecked && this.master == null &&
-          tries < numRetries;
-        tries++) {
-          
-          masterLocation = new HServerAddress(this.conf.get(MASTER_ADDRESS,
-            DEFAULT_MASTER_ADDRESS));
-          try {
-            HMasterInterface tryMaster = (HMasterInterface)HbaseRPC.getProxy(
-                HMasterInterface.class, HMasterInterface.versionID, 
-                masterLocation.getInetSocketAddress(), this.conf);
-            
-            if (tryMaster.isMasterRunning()) {
-              this.master = tryMaster;
-              break;
-            }
-            
-          } catch (IOException e) {
-            if(tries == numRetries - 1) {
-              // This was our last chance - don't bother sleeping
-              break;
-            }
-            LOG.info("Attempt " + tries + " of " + this.numRetries +
-              " failed with <" + e + ">. Retrying after sleep of " + this.pause);
-          }
-
-          // We either cannot connect to master or it is not running. Sleep & retry
-          
-          try {
-            Thread.sleep(this.pause);
-          } catch (InterruptedException e) {
-            // continue
-          }
-        }
-        this.masterChecked = true;
-      }
-      if (this.master == null) {
-        if (masterLocation == null) {
-          throw new MasterNotRunningException();
-        }
-        throw new MasterNotRunningException(masterLocation.toString());
-      }
-      return this.master;
-    }
-
-    /** {@inheritDoc} */
-    public boolean isMasterRunning() {
-      if (this.master == null) {
-        try {
-          getMaster();
-          
-        } catch (MasterNotRunningException e) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    /** {@inheritDoc} */
-    public boolean tableExists(final byte [] tableName)
-    throws MasterNotRunningException {
-      getMaster();
-      if (tableName == null) {
-        throw new IllegalArgumentException("Table name cannot be null");
-      }
-      if (isMetaTableName(tableName)) {
-        return true;
-      }
-      boolean exists = false;
-      try {
-        HTableDescriptor[] tables = listTables();
-        for (int i = 0; i < tables.length; i++) {
-          if (Bytes.equals(tables[i].getName(), tableName)) {
-            exists = true;
-          }
-        }
-      } catch (IOException e) {
-        LOG.warn("Testing for table existence threw exception", e);
-      }
-      return exists;
-    }
-    
-    /*
-     * @param n
-     * @return Truen if passed tablename <code>n</code> is equal to the name
-     * of a catalog table.
-     */
-    private static boolean isMetaTableName(final byte [] n) {
-      return Bytes.equals(n, ROOT_TABLE_NAME) ||
-        Bytes.equals(n, META_TABLE_NAME);
-    }
-
-    /** {@inheritDoc} */
-    public HRegionLocation getRegionLocation(final byte [] name,
-        final byte [] row, boolean reload)
-    throws IOException {
-      getMaster();
-      return reload? relocateRegion(name, row): locateRegion(name, row);
-    }
-
-    /** {@inheritDoc} */
-    public HTableDescriptor[] listTables() throws IOException {
-      getMaster();
-      final HashSet<HTableDescriptor> uniqueTables =
-        new HashSet<HTableDescriptor>();
-
-      MetaScannerVisitor visitor = new MetaScannerVisitor() {
-
-        /** {@inheritDoc} */
-        public boolean processRow(RowResult rowResult) throws IOException {
-          HRegionInfo info = Writables.getHRegionInfo(
-              rowResult.get(COL_REGIONINFO));
-
-          // Only examine the rows where the startKey is zero length
-          if (info.getStartKey().length == 0) {
-            uniqueTables.add(info.getTableDesc());
-          }
-          return true;
-        }
-
-      };
-      MetaScanner.metaScan(conf, visitor);
-
-      return uniqueTables.toArray(new HTableDescriptor[uniqueTables.size()]);
-    }
-
-    /** {@inheritDoc} */
-    public boolean isTableEnabled(byte[] tableName) throws IOException {
-      if (!tableExists(tableName)) {
-        throw new TableNotFoundException(Bytes.toString(tableName));
-      }
-      if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) {
-        // The root region is always enabled
-        return true;
-      }
-      
-      boolean result = true;
-      int rowsScanned = 0;
-      byte[] startKey =
-        HRegionInfo.createRegionName(tableName, null, HConstants.ZEROES);
-      HRegionInfo currentRegion = null;
-      do {
-        if (currentRegion != null) {
-          byte[] endKey = currentRegion.getEndKey();
-          if (endKey == null ||
-              Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)) {
-            // We have reached the end of the table and we're done
-            break;
-          }
-        }
-        HRegionInfo oldRegion = currentRegion;
-        if (oldRegion != null) {
-          startKey = oldRegion.getEndKey();
-        }
-        ScannerCallable s = new ScannerCallable(this, 
-            (Bytes.equals(tableName, HConstants.META_TABLE_NAME) ?
-                HConstants.ROOT_TABLE_NAME : HConstants.META_TABLE_NAME),
-            HConstants.COL_REGIONINFO_ARRAY, startKey,
-            HConstants.LATEST_TIMESTAMP, null
-        );
-        // Open scanner
-        getRegionServerWithRetries(s);
-        currentRegion = s.getHRegionInfo();
-        try {
-          RowResult r = null;
-          while (result && (r = getRegionServerWithRetries(s)) != null) {
-            Cell c = r.get(HConstants.COL_REGIONINFO);
-            if (c != null) {
-              byte[] value = c.getValue();
-              if (value != null) {
-                HRegionInfo info = Writables.getHRegionInfoOrNull(value);
-                if (info != null) {
-                  if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
-                    rowsScanned += 1;
-                    result = !info.isOffline();
-                  }
-                }
-              }
-            }
-          }
-        } finally {
-          s.setClose();
-          getRegionServerWithRetries(s);
-        }
-      } while (result);
-      return rowsScanned > 0 && result;
-    }
-    
-    /** {@inheritDoc} */
-    public HTableDescriptor getHTableDescriptor(byte[] tableName)
-    throws IOException {
-      if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) {
-        return new UnmodifyableHTableDescriptor(HTableDescriptor.ROOT_TABLEDESC);
-      }
-      if (!tableExists(tableName)) {
-        throw new TableNotFoundException(Bytes.toString(tableName));
-      }
-      byte[] startKey =
-        HRegionInfo.createRegionName(tableName, null, HConstants.ZEROES);
-      
-      HTableDescriptor result = null;
-      HRegionInfo currentRegion = null;
-      ScannerCallable s = null;
-      while (result == null) {
-        if (currentRegion != null) {
-          byte[] endKey = currentRegion.getEndKey();
-          if (endKey == null ||
-              Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)) {
-            // We have reached the end of the table and we're done
-            break;
-          }
-        }
-        HRegionInfo oldRegion = currentRegion;
-        if (oldRegion != null) {
-          startKey = oldRegion.getEndKey();
-        }
-        s = new ScannerCallable(this, 
-            (Bytes.equals(tableName, HConstants.META_TABLE_NAME) ?
-                HConstants.ROOT_TABLE_NAME : HConstants.META_TABLE_NAME),
-            HConstants.COL_REGIONINFO_ARRAY, startKey,
-            HConstants.LATEST_TIMESTAMP, null
-        );
-        // Open scanner
-        getRegionServerWithRetries(s);
-        currentRegion = s.getHRegionInfo();
-        try {
-          RowResult r = null;
-          while ((r = getRegionServerWithRetries(s)) != null) {
-            Cell c = r.get(HConstants.COL_REGIONINFO);
-            if (c != null) {
-              HRegionInfo info = Writables.getHRegionInfoOrNull(c.getValue());
-              if (info != null) {
-                if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
-                  result = new UnmodifyableHTableDescriptor(info.getTableDesc());
-                  break;
-                }
-              }
-            }
-          }
-        } finally {
-          s.setClose();
-          getRegionServerWithRetries(s);
-        }
-      }
-      return result;
-    }
-
-    /** {@inheritDoc} */
-    public HRegionLocation locateRegion(final byte [] tableName,
-        final byte [] row)
-    throws IOException{
-      getMaster();
-      return locateRegion(tableName, row, true);
-    }
-
-    /** {@inheritDoc} */
-    public HRegionLocation relocateRegion(final byte [] tableName,
-        final byte [] row)
-    throws IOException{
-      getMaster();
-      return locateRegion(tableName, row, false);
-    }
-
-    private HRegionLocation locateRegion(final byte [] tableName,
-        final byte [] row, boolean useCache)
-    throws IOException{
-      if (tableName == null || tableName.length == 0) {
-        throw new IllegalArgumentException(
-            "table name cannot be null or zero length");
-      }
-            
-      if (Bytes.equals(tableName, ROOT_TABLE_NAME)) {
-        synchronized (rootRegionLock) {
-          // This block guards against two threads trying to find the root
-          // region at the same time. One will go do the find while the 
-          // second waits. The second thread will not do find.
-          
-          if (!useCache || rootRegionLocation == null) {
-            return locateRootRegion();
-          }
-          return rootRegionLocation;
-        }        
-      } else if (Bytes.equals(tableName, META_TABLE_NAME)) {
-        synchronized (metaRegionLock) {
-          // This block guards against two threads trying to load the meta 
-          // region at the same time. The first will load the meta region and
-          // the second will use the value that the first one found.
-
-          return locateRegionInMeta(ROOT_TABLE_NAME, tableName, row, useCache);
-        }
-      } else {
-        synchronized(userRegionLock){
-          return locateRegionInMeta(META_TABLE_NAME, tableName, row, useCache);
-        }
-      }
-    }
-
-    /**
-      * Search one of the meta tables (-ROOT- or .META.) for the HRegionLocation
-      * info that contains the table and row we're seeking.
-      */
-    private HRegionLocation locateRegionInMeta(final byte [] parentTable,
-      final byte [] tableName, final byte [] row, boolean useCache)
-    throws IOException{
-      HRegionLocation location = null;
-      
-      // if we're supposed to be using the cache, then check it for a possible
-      // hit. otherwise, 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);
-      }
-
-      // build the key of the meta region we should be looking for.
-      // the extra 9's on the end are necessary to allow "exact" matches
-      // without knowing the precise region names.
-      byte [] metaKey = HRegionInfo.createRegionName(tableName, row,
-        HConstants.NINES);
-      for (int tries = 0; true; tries++) {
-        if (tries >= numRetries) {
-          throw new NoServerForRegionException("Unable to find region for " 
-            + Bytes.toString(row) + " after " + numRetries + " tries.");
-        }
-
-        try{
-          // locate the root region
-          HRegionLocation metaLocation = locateRegion(parentTable, metaKey);
-          HRegionInterface server = 
-            getHRegionConnection(metaLocation.getServerAddress());
-
-          // query the root region for the location of the meta region
-          RowResult regionInfoRow = server.getClosestRowBefore(
-            metaLocation.getRegionInfo().getRegionName(), metaKey);
-
-          if (regionInfoRow == null) {
-            throw new TableNotFoundException(Bytes.toString(tableName));
-          }
-
-          Cell value = regionInfoRow.get(COL_REGIONINFO);
-
-          if (value == null || value.getValue().length == 0) {
-            throw new IOException("HRegionInfo was null or empty in " + 
-              Bytes.toString(parentTable));
-          }
-
-          // convert the row result into the HRegionLocation we need!
-          HRegionInfo regionInfo = (HRegionInfo) Writables.getWritable(
-              value.getValue(), new HRegionInfo());
-
-          // possible we got a region of a different table...
-          if (!Bytes.equals(regionInfo.getTableDesc().getName(), tableName)) {
-            throw new TableNotFoundException(
-              "Table '" + Bytes.toString(tableName) + "' was not found.");
-          }
-
-          if (regionInfo.isOffline()) {
-            throw new RegionOfflineException("region offline: " + 
-              regionInfo.getRegionNameAsString());
-          }
-          
-          String serverAddress = 
-            Writables.cellToString(regionInfoRow.get(COL_SERVER));
-        
-          if (serverAddress.equals("")) { 
-            throw new NoServerForRegionException("No server address listed " +
-              "in " + Bytes.toString(parentTable) + " for region " +
-              regionInfo.getRegionNameAsString());
-          }
-        
-          // instantiate the location
-          location = new HRegionLocation(regionInfo, 
-            new HServerAddress(serverAddress));
-      
-          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 = RemoteExceptionHandler.decodeRemoteException(
-                (RemoteException) e);
-          }
-          if (tries < numRetries - 1) {
-            if (LOG.isDebugEnabled()) {
-              LOG.debug("reloading table servers because: " + e.getMessage());
-            }
-            relocateRegion(parentTable, metaKey);
-          } else {
-            throw e;
-          }
-        }
-      
-        try{
-          Thread.sleep(pause);              
-        } catch (InterruptedException e){
-          // continue
-        }
-      }
-    }
-
-    /*
-     * Search the cache for a location that fits our table and row key.
-     * Return null if no suitable region is located. TODO: synchronization note
-     * 
-     * <p>TODO: This method during writing consumes 15% of CPU doing lookup
-     * into the Soft Reference SortedMap.  Improve.
-     * 
-     * @param tableName
-     * @param row
-     * @return Null or region location found in cache.
-     */
-    private HRegionLocation getCachedLocation(final byte [] tableName,
-        final byte [] row) {
-      // find the map of cached locations for this table
-      Integer key = Bytes.mapKey(tableName);
-      SoftSortedMap<byte [], HRegionLocation> tableLocations =
-        cachedRegionLocations.get(key);
-
-      // if tableLocations for this table isn't built yet, make one
-      if (tableLocations == null) {
-        tableLocations = new SoftSortedMap<byte [],
-          HRegionLocation>(Bytes.BYTES_COMPARATOR);
-        cachedRegionLocations.put(key, tableLocations);
-      }
-
-      // 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;
-      }
-
-      HRegionLocation rl = tableLocations.get(row);
-      if (rl != null) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Cache hit in table locations for row <" +
-            Bytes.toString(row) +
-            "> and tableName " + Bytes.toString(tableName) +
-            ": location server " + rl.getServerAddress() +
-            ", location region name " +
-            rl.getRegionInfo().getRegionNameAsString());
-        }
-        return rl;
-      }
-
-      // Cut the cache so that we only get the part that could contain
-      // regions that match our key
-      SoftSortedMap<byte[], HRegionLocation> matchingRegions =
-        tableLocations.headMap(row);
-
-      // if that portion of the map is empty, then we're done. otherwise,
-      // we need to examine the cached location to verify that it is
-      // a match by end key as well.
-      if (!matchingRegions.isEmpty()) {
-        HRegionLocation possibleRegion =
-          matchingRegions.get(matchingRegions.lastKey());
-
-        // there is a possibility that the reference was garbage collected
-        // in the instant since we checked isEmpty().
-        if (possibleRegion != null) {
-          byte[] endKey = possibleRegion.getRegionInfo().getEndKey();
-
-          // make sure that the end key is greater than the row we're looking
-          // for, otherwise the row actually belongs in the next region, not
-          // this one. the exception case is when the endkey is EMPTY_START_ROW,
-          // signifying that the region we're checking is actually the last
-          // region in the table.
-          if (Bytes.equals(endKey, HConstants.EMPTY_END_ROW) ||
-              Bytes.compareTo(endKey, row) > 0) {
-            return possibleRegion;
-          }
-        }
-      }
-
-      // Passed all the way through, so we got nothin - complete cache miss
-      return null;
-    }
-
-    /**
-     * Delete a cached location, if it satisfies the table name and row
-     * requirements.
-     */
-    private void deleteCachedLocation(final byte [] tableName,
-        final byte [] row) {
-      // find the map of cached locations for this table
-      Integer key = Bytes.mapKey(tableName);
-      SoftSortedMap<byte [], HRegionLocation> tableLocations = 
-        cachedRegionLocations.get(key);
-
-      // if tableLocations for this table isn't built yet, make one
-      if (tableLocations == null) {
-        tableLocations =
-          new SoftSortedMap<byte [], HRegionLocation>(Bytes.BYTES_COMPARATOR);
-        cachedRegionLocations.put(key, tableLocations);
-      }
-
-      // start to examine the cache. we can only do cache actions
-      // if there's something in the cache for this table.
-      if (!tableLocations.isEmpty()) {
-        // cut the cache so that we only get the part that could contain
-        // regions that match our key
-        SoftSortedMap<byte [], HRegionLocation> matchingRegions =
-          tableLocations.headMap(row);
-
-        // if that portion of the map is empty, then we're done. otherwise,
-        // we need to examine the cached location to verify that it is 
-        // a match by end key as well.
-        if (!matchingRegions.isEmpty()) {
-          HRegionLocation possibleRegion = 
-            matchingRegions.get(matchingRegions.lastKey());
-          
-          byte [] endKey = possibleRegion.getRegionInfo().getEndKey();
-          
-          // by nature of the map, we know that the start key has to be < 
-          // otherwise it wouldn't be in the headMap. 
-          if (Bytes.compareTo(endKey, row) <= 0) {
-            // delete any matching entry
-            HRegionLocation rl = 
-              tableLocations.remove(matchingRegions.lastKey());
-            if (rl != null && LOG.isDebugEnabled()) {
-              LOG.debug("Removed " + rl.getRegionInfo().getRegionNameAsString() +
-                " from cache because of " + Bytes.toString(row));
-            }
-          }
-        }
-      }
-    }
-
-    /**
-      * Put a newly discovered HRegionLocation into the cache.
-      */
-    private void cacheLocation(final byte [] tableName,
-        final HRegionLocation location){
-      byte [] startKey = location.getRegionInfo().getStartKey();
-      
-      // find the map of cached locations for this table
-      Integer key = Bytes.mapKey(tableName);
-      SoftSortedMap<byte [], HRegionLocation> tableLocations = 
-        cachedRegionLocations.get(key);
-
-      // if tableLocations for this table isn't built yet, make one
-      if (tableLocations == null) {
-        tableLocations =
-          new SoftSortedMap<byte [], HRegionLocation>(Bytes.BYTES_COMPARATOR);
-        cachedRegionLocations.put(key, tableLocations);
-      }
-      
-      // save the HRegionLocation under the startKey
-      tableLocations.put(startKey, location);
-    }
-    
-    /** {@inheritDoc} */
-    public HRegionInterface getHRegionConnection(HServerAddress regionServer) 
-    throws IOException {
-      getMaster();
-      HRegionInterface server;
-      synchronized (this.servers) {
-        // See if we already have a connection
-        server = this.servers.get(regionServer.toString());
-        if (server == null) { // Get a connection
-          long versionId = 0;
-          try {
-            versionId =
-              serverInterfaceClass.getDeclaredField("versionID").getLong(server);
-          } catch (IllegalAccessException e) {
-            // Should never happen unless visibility of versionID changes
-            throw new UnsupportedOperationException(
-                "Unable to open a connection to a " +
-                serverInterfaceClass.getName() + " server.", e);
-          } catch (NoSuchFieldException e) {
-            // Should never happen unless versionID field name changes in HRegionInterface
-            throw new UnsupportedOperationException(
-                "Unable to open a connection to a " +
-                serverInterfaceClass.getName() + " server.", e);
-          }
-
-          try {
-            server = (HRegionInterface)HbaseRPC.waitForProxy(serverInterfaceClass,
-                versionId, regionServer.getInetSocketAddress(), this.conf, 
-                this.maxRPCAttempts);
-          } catch (RemoteException e) {
-            throw RemoteExceptionHandler.decodeRemoteException(e);
-          }
-          this.servers.put(regionServer.toString(), server);
-        }
-      }
-      return server;
-    }
-
-    /*
-     * Repeatedly try to find the root region by asking the master for where it is
-     * @return HRegionLocation for root region if found
-     * @throws NoServerForRegionException - if the root region can not be located
-     * after retrying
-     * @throws IOException 
-     */
-    private HRegionLocation locateRootRegion()
-    throws IOException {
-      getMaster();
-      HServerAddress rootRegionAddress = null;
-      for (int tries = 0; tries < numRetries; tries++) {
-        int localTimeouts = 0;
-        
-        // ask the master which server has the root region
-        while (rootRegionAddress == null && localTimeouts < numRetries) {
-          rootRegionAddress = master.findRootRegion();
-          if (rootRegionAddress == null) {
-            try {
-              if (LOG.isDebugEnabled()) {
-                LOG.debug("Sleeping. Waiting for root region.");
-              }
-              Thread.sleep(pause);
-              if (LOG.isDebugEnabled()) {
-                LOG.debug("Wake. Retry finding root region.");
-              }
-            } catch (InterruptedException iex) {
-              // continue
-            }
-            localTimeouts++;
-          }
-        }
-
-        if (rootRegionAddress == null) {
-          throw new NoServerForRegionException(
-              "Timed out trying to locate root region");
-        }
-
-        // get a connection to the region server
-        HRegionInterface server = getHRegionConnection(rootRegionAddress);
-
-        try {
-          // if this works, then we're good, and we have an acceptable address,
-          // so we can stop doing retries and return the result.
-          server.getRegionInfo(HRegionInfo.ROOT_REGIONINFO.getRegionName());
-          if (LOG.isDebugEnabled()) {
-            LOG.debug("Found ROOT " + HRegionInfo.ROOT_REGIONINFO);
-          }
-          break;
-        } catch (IOException e) {
-          if (tries == numRetries - 1) {
-            // Don't bother sleeping. We've run out of retries.
-            if (e instanceof RemoteException) {
-              e = RemoteExceptionHandler.decodeRemoteException(
-                  (RemoteException) e);
-            }
-            throw e;
-          }
-          
-          // Sleep and retry finding root region.
-          try {
-            if (LOG.isDebugEnabled()) {
-              LOG.debug("Root region location changed. Sleeping.");
-            }
-            Thread.sleep(pause);
-            if (LOG.isDebugEnabled()) {
-              LOG.debug("Wake. Retry finding root region.");
-            }
-          } catch (InterruptedException iex) {
-            // continue
-          }
-        }
-        
-        rootRegionAddress = null;
-      }
-      
-      // if the address is null by this point, then the retries have failed,
-      // and we're sort of sunk
-      if (rootRegionAddress == null) {
-        throw new NoServerForRegionException(
-          "unable to locate root region server");
-      }
-      
-      // return the region location
-      return new HRegionLocation(
-        HRegionInfo.ROOT_REGIONINFO, rootRegionAddress);
-    }
-
-    /** {@inheritDoc} */
-    public <T> T getRegionServerWithRetries(ServerCallable<T> callable) 
-    throws IOException, RuntimeException {
-      getMaster();
-      List<Throwable> exceptions = new ArrayList<Throwable>();
-      for(int tries = 0; tries < numRetries; tries++) {
-        try {
-          callable.instantiateServer(tries != 0);
-          return callable.call();
-        } catch (Throwable t) {
-          if (t instanceof UndeclaredThrowableException) {
-            t = t.getCause();
-          }
-          if (t instanceof RemoteException) {
-            t = RemoteExceptionHandler.decodeRemoteException((RemoteException) t);
-          }
-          if (t instanceof DoNotRetryIOException) {
-            throw (DoNotRetryIOException)t;
-          }
-          exceptions.add(t);
-          if (tries == numRetries - 1) {
-            throw new RetriesExhaustedException(callable.getServerName(),
-                callable.getRegionName(), callable.getRow(), tries, exceptions);
-          }
-          if (LOG.isDebugEnabled()) {
-            LOG.debug("reloading table servers because: " + t.getMessage());
-          }
-        }
-        try {
-          Thread.sleep(pause);
-        } catch (InterruptedException e) {
-          // continue
-        }
-      }
-      return null;    
-    }
-  }
-}
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.client;
+
+import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.HServerAddress;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.LocalHBaseCluster;
+import org.apache.hadoop.hbase.MasterNotRunningException;
+import org.apache.hadoop.hbase.RemoteExceptionHandler;
+import org.apache.hadoop.hbase.TableNotFoundException;
+import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
+import org.apache.hadoop.hbase.io.Cell;
+import org.apache.hadoop.hbase.io.RowResult;
+import org.apache.hadoop.hbase.ipc.HMasterInterface;
+import org.apache.hadoop.hbase.ipc.HRegionInterface;
+import org.apache.hadoop.hbase.ipc.HbaseRPC;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.SoftSortedMap;
+import org.apache.hadoop.hbase.util.Writables;
+import org.apache.hadoop.ipc.RemoteException;
+
+/**
+ * A non-instantiable class that manages connections to multiple tables in
+ * multiple HBase instances.
+ * 
+ * Used by {@link HTable} and {@link HBaseAdmin}
+ */
+public class HConnectionManager implements HConstants {
+  /*
+   * Private. Not instantiable.
+   */
+  private HConnectionManager() {
+    super();
+  }
+  
+  // A Map of master HServerAddress -> connection information for that instance
+  // Note that although the Map is synchronized, the objects it contains
+  // are mutable and hence require synchronized access to them
+  private static final Map<String, TableServers> HBASE_INSTANCES =
+    new ConcurrentHashMap<String, TableServers>();
+
+  /**
+   * Get the connection object for the instance specified by the configuration
+   * If no current connection exists, create a new connection for that instance
+   * @param conf
+   * @return HConnection object for the instance specified by the configuration
+   */
+  public static HConnection getConnection(HBaseConfiguration conf) {
+    TableServers connection;
+    synchronized (HBASE_INSTANCES) {
+      String instanceName = conf.get(HBASE_DIR);
+      connection = HBASE_INSTANCES.get(instanceName);
+      if (connection == null) {
+        connection = new TableServers(conf);
+        HBASE_INSTANCES.put(instanceName, connection);
+      }
+    }
+    return connection;
+  }
+  
+  /**
+   * Delete connection information for the instance specified by the configuration
+   * @param conf
+   */
+  public static void deleteConnectionInfo(HBaseConfiguration conf) {
+    synchronized (HBASE_INSTANCES) {
+      HBASE_INSTANCES.remove(conf.get(HBASE_DIR));
+    }
+  }
+
+  /**
+   * Clear the static map of connection info.
+   */
+  public static void deleteConnectionInfo() {
+    synchronized (HBASE_INSTANCES) {
+      HBASE_INSTANCES.clear();
+    }
+  }
+
+  
+  /* Encapsulates finding the servers for an HBase instance */
+  private static class TableServers implements HConnection, HConstants {
+    private static final Log LOG = LogFactory.getLog(TableServers.class);
+    private final Class<? extends HRegionInterface> serverInterfaceClass;
+    private final long pause;
+    private final int numRetries;
+    private final int maxRPCAttempts;
+
+    private final Integer masterLock = new Integer(0);
+    private volatile boolean closed;
+    private volatile HMasterInterface master;
+    private volatile boolean masterChecked;
+    
+    private final Integer rootRegionLock = new Integer(0);
+    private final Integer metaRegionLock = new Integer(0);
+    private final Integer userRegionLock = new Integer(0);
+        
+    private volatile HBaseConfiguration conf;
+    
+    // Known region HServerAddress.toString() -> HRegionInterface 
+    private final Map<String, HRegionInterface> servers =
+      new ConcurrentHashMap<String, HRegionInterface>();
+
+    private HRegionLocation rootRegionLocation; 
+    
+    private final Map<Integer, SoftSortedMap<byte [], HRegionLocation>> 
+      cachedRegionLocations = Collections.synchronizedMap(
+         new HashMap<Integer, SoftSortedMap<byte [], HRegionLocation>>());
+    
+    /** 
+     * constructor
+     * @param conf Configuration object
+     */
+    @SuppressWarnings("unchecked")
+    public TableServers(HBaseConfiguration conf) {
+      this.conf = LocalHBaseCluster.doLocal(new HBaseConfiguration(conf));
+      
+      String serverClassName =
+        conf.get(REGION_SERVER_CLASS, DEFAULT_REGION_SERVER_CLASS);
+
+      this.closed = false;
+      
+      try {
+        this.serverInterfaceClass =
+          (Class<? extends HRegionInterface>) Class.forName(serverClassName);
+        
+      } catch (ClassNotFoundException e) {
+        throw new UnsupportedOperationException(
+            "Unable to find region server interface " + serverClassName, e);
+      }
+
+      this.pause = conf.getLong("hbase.client.pause", 10 * 1000);
+      this.numRetries = conf.getInt("hbase.client.retries.number", 10);
+      this.maxRPCAttempts = conf.getInt("hbase.client.rpc.maxattempts", 1);
+      
+      this.master = null;
+      this.masterChecked = false;
+    }
+
+    private long getPauseTime(int tries) {
+      if (tries >= HConstants.RETRY_BACKOFF.length)
+        tries = HConstants.RETRY_BACKOFF.length - 1;
+      return this.pause * HConstants.RETRY_BACKOFF[tries];
+    }
+
+    /** {@inheritDoc} */
+    public HMasterInterface getMaster() throws MasterNotRunningException {
+      HServerAddress masterLocation = null;
+      synchronized (this.masterLock) {
+        for (int tries = 0;
+          !this.closed &&
+          !this.masterChecked && this.master == null &&
+          tries < numRetries;
+        tries++) {
+          
+          masterLocation = new HServerAddress(this.conf.get(MASTER_ADDRESS,
+            DEFAULT_MASTER_ADDRESS));
+          try {
+            HMasterInterface tryMaster = (HMasterInterface)HbaseRPC.getProxy(
+                HMasterInterface.class, HMasterInterface.versionID, 
+                masterLocation.getInetSocketAddress(), this.conf);
+            
+            if (tryMaster.isMasterRunning()) {
+              this.master = tryMaster;
+              break;
+            }
+            
+          } catch (IOException e) {
+            if(tries == numRetries - 1) {
+              // This was our last chance - don't bother sleeping
+              break;
+            }
+            LOG.info("Attempt " + tries + " of " + this.numRetries +
+              " failed with <" + e + ">. Retrying after sleep of " +
+              getPauseTime(tries));
+          }
+
+          // We either cannot connect to master or it is not running. Sleep & retry
+          
+          try {
+            Thread.sleep(getPauseTime(tries));
+          } catch (InterruptedException e) {
+            // continue
+          }
+        }
+        this.masterChecked = true;
+      }
+      if (this.master == null) {
+        if (masterLocation == null) {
+          throw new MasterNotRunningException();
+        }
+        throw new MasterNotRunningException(masterLocation.toString());
+      }
+      return this.master;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isMasterRunning() {
+      if (this.master == null) {
+        try {
+          getMaster();
+          
+        } catch (MasterNotRunningException e) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean tableExists(final byte [] tableName)
+    throws MasterNotRunningException {
+      getMaster();
+      if (tableName == null) {
+        throw new IllegalArgumentException("Table name cannot be null");
+      }
+      if (isMetaTableName(tableName)) {
+        return true;
+      }
+      boolean exists = false;
+      try {
+        HTableDescriptor[] tables = listTables();
+        for (int i = 0; i < tables.length; i++) {
+          if (Bytes.equals(tables[i].getName(), tableName)) {
+            exists = true;
+          }
+        }
+      } catch (IOException e) {
+        LOG.warn("Testing for table existence threw exception", e);
+      }
+      return exists;
+    }
+    
+    /*
+     * @param n
+     * @return Truen if passed tablename <code>n</code> is equal to the name
+     * of a catalog table.
+     */
+    private static boolean isMetaTableName(final byte [] n) {
+      return Bytes.equals(n, ROOT_TABLE_NAME) ||
+        Bytes.equals(n, META_TABLE_NAME);
+    }
+
+    /** {@inheritDoc} */
+    public HRegionLocation getRegionLocation(final byte [] name,
+        final byte [] row, boolean reload)
+    throws IOException {
+      getMaster();
+      return reload? relocateRegion(name, row): locateRegion(name, row);
+    }
+
+    /** {@inheritDoc} */
+    public HTableDescriptor[] listTables() throws IOException {
+      getMaster();
+      final HashSet<HTableDescriptor> uniqueTables =
+        new HashSet<HTableDescriptor>();
+
+      MetaScannerVisitor visitor = new MetaScannerVisitor() {
+
+        /** {@inheritDoc} */
+        public boolean processRow(RowResult rowResult) throws IOException {
+          HRegionInfo info = Writables.getHRegionInfo(
+              rowResult.get(COL_REGIONINFO));
+
+          // Only examine the rows where the startKey is zero length
+          if (info.getStartKey().length == 0) {
+            uniqueTables.add(info.getTableDesc());
+          }
+          return true;
+        }
+
+      };
+      MetaScanner.metaScan(conf, visitor);
+
+      return uniqueTables.toArray(new HTableDescriptor[uniqueTables.size()]);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isTableEnabled(byte[] tableName) throws IOException {
+      if (!tableExists(tableName)) {
+        throw new TableNotFoundException(Bytes.toString(tableName));
+      }
+      if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) {
+        // The root region is always enabled
+        return true;
+      }
+      
+      boolean result = true;
+      int rowsScanned = 0;
+      byte[] startKey =
+        HRegionInfo.createRegionName(tableName, null, HConstants.ZEROES);
+      HRegionInfo currentRegion = null;
+      do {
+        if (currentRegion != null) {
+          byte[] endKey = currentRegion.getEndKey();
+          if (endKey == null ||
+              Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)) {
+            // We have reached the end of the table and we're done
+            break;
+          }
+        }
+        HRegionInfo oldRegion = currentRegion;
+        if (oldRegion != null) {
+          startKey = oldRegion.getEndKey();
+        }
+        ScannerCallable s = new ScannerCallable(this, 
+            (Bytes.equals(tableName, HConstants.META_TABLE_NAME) ?
+                HConstants.ROOT_TABLE_NAME : HConstants.META_TABLE_NAME),
+            HConstants.COL_REGIONINFO_ARRAY, startKey,
+            HConstants.LATEST_TIMESTAMP, null
+        );
+        // Open scanner
+        getRegionServerWithRetries(s);
+        currentRegion = s.getHRegionInfo();
+        try {
+          RowResult r = null;
+          while (result && (r = getRegionServerWithRetries(s)) != null) {
+            Cell c = r.get(HConstants.COL_REGIONINFO);
+            if (c != null) {
+              byte[] value = c.getValue();
+              if (value != null) {
+                HRegionInfo info = Writables.getHRegionInfoOrNull(value);
+                if (info != null) {
+                  if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
+                    rowsScanned += 1;
+                    result = !info.isOffline();
+                  }
+                }
+              }
+            }
+          }
+        } finally {
+          s.setClose();
+          getRegionServerWithRetries(s);
+        }
+      } while (result);
+      return rowsScanned > 0 && result;
+    }
+    
+    /** {@inheritDoc} */
+    public HTableDescriptor getHTableDescriptor(byte[] tableName)
+    throws IOException {
+      if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) {
+        return new UnmodifyableHTableDescriptor(HTableDescriptor.ROOT_TABLEDESC);
+      }
+      if (!tableExists(tableName)) {
+        throw new TableNotFoundException(Bytes.toString(tableName));
+      }
+      byte[] startKey =
+        HRegionInfo.createRegionName(tableName, null, HConstants.ZEROES);
+      
+      HTableDescriptor result = null;
+      HRegionInfo currentRegion = null;
+      ScannerCallable s = null;
+      while (result == null) {
+        if (currentRegion != null) {
+          byte[] endKey = currentRegion.getEndKey();
+          if (endKey == null ||
+              Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)) {
+            // We have reached the end of the table and we're done
+            break;
+          }
+        }
+        HRegionInfo oldRegion = currentRegion;
+        if (oldRegion != null) {
+          startKey = oldRegion.getEndKey();
+        }
+        s = new ScannerCallable(this, 
+            (Bytes.equals(tableName, HConstants.META_TABLE_NAME) ?
+                HConstants.ROOT_TABLE_NAME : HConstants.META_TABLE_NAME),
+            HConstants.COL_REGIONINFO_ARRAY, startKey,
+            HConstants.LATEST_TIMESTAMP, null
+        );
+        // Open scanner
+        getRegionServerWithRetries(s);
+        currentRegion = s.getHRegionInfo();
+        try {
+          RowResult r = null;
+          while ((r = getRegionServerWithRetries(s)) != null) {
+            Cell c = r.get(HConstants.COL_REGIONINFO);
+            if (c != null) {
+              HRegionInfo info = Writables.getHRegionInfoOrNull(c.getValue());
+              if (info != null) {
+                if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
+                  result = new UnmodifyableHTableDescriptor(info.getTableDesc());
+                  break;
+                }
+              }
+            }
+          }
+        } finally {
+          s.setClose();
+          getRegionServerWithRetries(s);
+        }
+      }
+      return result;
+    }
+
+    /** {@inheritDoc} */
+    public HRegionLocation locateRegion(final byte [] tableName,
+        final byte [] row)
+    throws IOException{
+      getMaster();
+      return locateRegion(tableName, row, true);
+    }
+
+    /** {@inheritDoc} */
+    public HRegionLocation relocateRegion(final byte [] tableName,
+        final byte [] row)
+    throws IOException{
+      getMaster();
+      return locateRegion(tableName, row, false);
+    }
+
+    private HRegionLocation locateRegion(final byte [] tableName,
+        final byte [] row, boolean useCache)
+    throws IOException{
+      if (tableName == null || tableName.length == 0) {
+        throw new IllegalArgumentException(
+            "table name cannot be null or zero length");
+      }
+            
+      if (Bytes.equals(tableName, ROOT_TABLE_NAME)) {
+        synchronized (rootRegionLock) {
+          // This block guards against two threads trying to find the root
+          // region at the same time. One will go do the find while the 
+          // second waits. The second thread will not do find.
+          
+          if (!useCache || rootRegionLocation == null) {
+            return locateRootRegion();
+          }
+          return rootRegionLocation;
+        }        
+      } else if (Bytes.equals(tableName, META_TABLE_NAME)) {
+        synchronized (metaRegionLock) {
+          // This block guards against two threads trying to load the meta 
+          // region at the same time. The first will load the meta region and
+          // the second will use the value that the first one found.
+
+          return locateRegionInMeta(ROOT_TABLE_NAME, tableName, row, useCache);
+        }
+      } else {
+        synchronized(userRegionLock){
+          return locateRegionInMeta(META_TABLE_NAME, tableName, row, useCache);
+        }
+      }
+    }
+
+    /**
+      * Search one of the meta tables (-ROOT- or .META.) for the HRegionLocation
+      * info that contains the table and row we're seeking.
+      */
+    private HRegionLocation locateRegionInMeta(final byte [] parentTable,
+      final byte [] tableName, final byte [] row, boolean useCache)
+    throws IOException{
+      HRegionLocation location = null;
+      
+      // if we're supposed to be using the cache, then check it for a possible
+      // hit. otherwise, 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);
+      }
+
+      // build the key of the meta region we should be looking for.
+      // the extra 9's on the end are necessary to allow "exact" matches
+      // without knowing the precise region names.
+      byte [] metaKey = HRegionInfo.createRegionName(tableName, row,
+        HConstants.NINES);
+      for (int tries = 0; true; tries++) {
+        if (tries >= numRetries) {
+          throw new NoServerForRegionException("Unable to find region for " 
+            + Bytes.toString(row) + " after " + numRetries + " tries.");
+        }
+
+        try{
+          // locate the root region
+          HRegionLocation metaLocation = locateRegion(parentTable, metaKey);
+          HRegionInterface server = 
+            getHRegionConnection(metaLocation.getServerAddress());
+
+          // query the root region for the location of the meta region
+          RowResult regionInfoRow = server.getClosestRowBefore(
+            metaLocation.getRegionInfo().getRegionName(), metaKey);
+
+          if (regionInfoRow == null) {
+            throw new TableNotFoundException(Bytes.toString(tableName));
+          }
+
+          Cell value = regionInfoRow.get(COL_REGIONINFO);
+
+          if (value == null || value.getValue().length == 0) {
+            throw new IOException("HRegionInfo was null or empty in " + 
+              Bytes.toString(parentTable));
+          }
+
+          // convert the row result into the HRegionLocation we need!
+          HRegionInfo regionInfo = (HRegionInfo) Writables.getWritable(
+              value.getValue(), new HRegionInfo());
+
+          // possible we got a region of a different table...
+          if (!Bytes.equals(regionInfo.getTableDesc().getName(), tableName)) {
+            throw new TableNotFoundException(
+              "Table '" + Bytes.toString(tableName) + "' was not found.");
+          }
+
+          if (regionInfo.isOffline()) {
+            throw new RegionOfflineException("region offline: " + 
+              regionInfo.getRegionNameAsString());
+          }
+          
+          String serverAddress = 
+            Writables.cellToString(regionInfoRow.get(COL_SERVER));
+        
+          if (serverAddress.equals("")) { 
+            throw new NoServerForRegionException("No server address listed " +
+              "in " + Bytes.toString(parentTable) + " for region " +
+              regionInfo.getRegionNameAsString());
+          }
+        
+          // instantiate the location
+          location = new HRegionLocation(regionInfo, 
+            new HServerAddress(serverAddress));
+      
+          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 = RemoteExceptionHandler.decodeRemoteException(
+                (RemoteException) e);
+          }
+          if (tries < numRetries - 1) {
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("reloading table servers because: " + e.getMessage());
+            }
+            relocateRegion(parentTable, metaKey);
+          } else {
+            throw e;
+          }
+        }
+      
+        try{
+          Thread.sleep(getPauseTime(tries));              
+        } catch (InterruptedException e){
+          // continue
+        }
+      }
+    }
+
+    /*
+     * Search the cache for a location that fits our table and row key.
+     * Return null if no suitable region is located. TODO: synchronization note
+     * 
+     * <p>TODO: This method during writing consumes 15% of CPU doing lookup
+     * into the Soft Reference SortedMap.  Improve.
+     * 
+     * @param tableName
+     * @param row
+     * @return Null or region location found in cache.
+     */
+    private HRegionLocation getCachedLocation(final byte [] tableName,
+        final byte [] row) {
+      // find the map of cached locations for this table
+      Integer key = Bytes.mapKey(tableName);
+      SoftSortedMap<byte [], HRegionLocation> tableLocations =
+        cachedRegionLocations.get(key);
+
+      // if tableLocations for this table isn't built yet, make one
+      if (tableLocations == null) {
+        tableLocations = new SoftSortedMap<byte [],
+          HRegionLocation>(Bytes.BYTES_COMPARATOR);
+        cachedRegionLocations.put(key, tableLocations);
+      }
+
+      // 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;
+      }
+
+      HRegionLocation rl = tableLocations.get(row);
+      if (rl != null) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Cache hit in table locations for row <" +
+            Bytes.toString(row) +
+            "> and tableName " + Bytes.toString(tableName) +
+            ": location server " + rl.getServerAddress() +
+            ", location region name " +
+            rl.getRegionInfo().getRegionNameAsString());
+        }
+        return rl;
+      }
+
+      // Cut the cache so that we only get the part that could contain
+      // regions that match our key
+      SoftSortedMap<byte[], HRegionLocation> matchingRegions =
+        tableLocations.headMap(row);
+
+      // if that portion of the map is empty, then we're done. otherwise,
+      // we need to examine the cached location to verify that it is
+      // a match by end key as well.
+      if (!matchingRegions.isEmpty()) {
+        HRegionLocation possibleRegion =
+          matchingRegions.get(matchingRegions.lastKey());
+
+        // there is a possibility that the reference was garbage collected
+        // in the instant since we checked isEmpty().
+        if (possibleRegion != null) {
+          byte[] endKey = possibleRegion.getRegionInfo().getEndKey();
+
+          // make sure that the end key is greater than the row we're looking
+          // for, otherwise the row actually belongs in the next region, not
+          // this one. the exception case is when the endkey is EMPTY_START_ROW,
+          // signifying that the region we're checking is actually the last
+          // region in the table.
+          if (Bytes.equals(endKey, HConstants.EMPTY_END_ROW) ||
+              Bytes.compareTo(endKey, row) > 0) {
+            return possibleRegion;
+          }
+        }
+      }
+
+      // Passed all the way through, so we got nothin - complete cache miss
+      return null;
+    }
+
+    /**
+     * Delete a cached location, if it satisfies the table name and row
+     * requirements.
+     */
+    private void deleteCachedLocation(final byte [] tableName,
+        final byte [] row) {
+      // find the map of cached locations for this table
+      Integer key = Bytes.mapKey(tableName);
+      SoftSortedMap<byte [], HRegionLocation> tableLocations = 
+        cachedRegionLocations.get(key);
+
+      // if tableLocations for this table isn't built yet, make one
+      if (tableLocations == null) {
+        tableLocations =
+          new SoftSortedMap<byte [], HRegionLocation>(Bytes.BYTES_COMPARATOR);
+        cachedRegionLocations.put(key, tableLocations);
+      }
+
+      // start to examine the cache. we can only do cache actions
+      // if there's something in the cache for this table.
+      if (!tableLocations.isEmpty()) {
+        // cut the cache so that we only get the part that could contain
+        // regions that match our key
+        SoftSortedMap<byte [], HRegionLocation> matchingRegions =
+          tableLocations.headMap(row);
+
+        // if that portion of the map is empty, then we're done. otherwise,
+        // we need to examine the cached location to verify that it is 
+        // a match by end key as well.
+        if (!matchingRegions.isEmpty()) {
+          HRegionLocation possibleRegion = 
+            matchingRegions.get(matchingRegions.lastKey());
+          
+          byte [] endKey = possibleRegion.getRegionInfo().getEndKey();
+          
+          // by nature of the map, we know that the start key has to be < 
+          // otherwise it wouldn't be in the headMap. 
+          if (Bytes.compareTo(endKey, row) <= 0) {
+            // delete any matching entry
+            HRegionLocation rl = 
+              tableLocations.remove(matchingRegions.lastKey());
+            if (rl != null && LOG.isDebugEnabled()) {
+              LOG.debug("Removed " + rl.getRegionInfo().getRegionNameAsString() +
+                " from cache because of " + Bytes.toString(row));
+            }
+          }
+        }
+      }
+    }
+
+    /**
+      * Put a newly discovered HRegionLocation into the cache.
+      */
+    private void cacheLocation(final byte [] tableName,
+        final HRegionLocation location){
+      byte [] startKey = location.getRegionInfo().getStartKey();
+      
+      // find the map of cached locations for this table
+      Integer key = Bytes.mapKey(tableName);
+      SoftSortedMap<byte [], HRegionLocation> tableLocations = 
+        cachedRegionLocations.get(key);
+
+      // if tableLocations for this table isn't built yet, make one
+      if (tableLocations == null) {
+        tableLocations =
+          new SoftSortedMap<byte [], HRegionLocation>(Bytes.BYTES_COMPARATOR);
+        cachedRegionLocations.put(key, tableLocations);
+      }
+      
+      // save the HRegionLocation under the startKey
+      tableLocations.put(startKey, location);
+    }
+    
+    /** {@inheritDoc} */
+    public HRegionInterface getHRegionConnection(HServerAddress regionServer) 
+    throws IOException {
+      getMaster();
+      HRegionInterface server;
+      synchronized (this.servers) {
+        // See if we already have a connection
+        server = this.servers.get(regionServer.toString());
+        if (server == null) { // Get a connection
+          long versionId = 0;
+          try {
+            versionId =
+              serverInterfaceClass.getDeclaredField("versionID").getLong(server);
+          } catch (IllegalAccessException e) {
+            // Should never happen unless visibility of versionID changes
+            throw new UnsupportedOperationException(
+                "Unable to open a connection to a " +
+                serverInterfaceClass.getName() + " server.", e);
+          } catch (NoSuchFieldException e) {
+            // Should never happen unless versionID field name changes in HRegionInterface
+            throw new UnsupportedOperationException(
+                "Unable to open a connection to a " +
+                serverInterfaceClass.getName() + " server.", e);
+          }
+
+          try {
+            server = (HRegionInterface)HbaseRPC.waitForProxy(serverInterfaceClass,
+                versionId, regionServer.getInetSocketAddress(), this.conf, 
+                this.maxRPCAttempts);
+          } catch (RemoteException e) {
+            throw RemoteExceptionHandler.decodeRemoteException(e);
+          }
+          this.servers.put(regionServer.toString(), server);
+        }
+      }
+      return server;
+    }
+
+    /*
+     * Repeatedly try to find the root region by asking the master for where it is
+     * @return HRegionLocation for root region if found
+     * @throws NoServerForRegionException - if the root region can not be located
+     * after retrying
+     * @throws IOException 
+     */
+    private HRegionLocation locateRootRegion()
+    throws IOException {
+      getMaster();
+      HServerAddress rootRegionAddress = null;
+      for (int tries = 0; tries < numRetries; tries++) {
+        int localTimeouts = 0;
+        
+        // ask the master which server has the root region
+        while (rootRegionAddress == null && localTimeouts < numRetries) {
+          rootRegionAddress = master.findRootRegion();
+          if (rootRegionAddress == null) {
+            try {
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Sleeping. Waiting for root region.");
+              }
+              Thread.sleep(getPauseTime(tries));
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Wake. Retry finding root region.");
+              }
+            } catch (InterruptedException iex) {
+              // continue
+            }
+            localTimeouts++;
+          }
+        }
+
+        if (rootRegionAddress == null) {
+          throw new NoServerForRegionException(
+              "Timed out trying to locate root region");
+        }
+
+        // get a connection to the region server
+        HRegionInterface server = getHRegionConnection(rootRegionAddress);
+
+        try {
+          // if this works, then we're good, and we have an acceptable address,
+          // so we can stop doing retries and return the result.
+          server.getRegionInfo(HRegionInfo.ROOT_REGIONINFO.getRegionName());
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Found ROOT " + HRegionInfo.ROOT_REGIONINFO);
+          }
+          break;
+        } catch (IOException e) {
+          if (tries == numRetries - 1) {
+            // Don't bother sleeping. We've run out of retries.
+            if (e instanceof RemoteException) {
+              e = RemoteExceptionHandler.decodeRemoteException(
+                  (RemoteException) e);
+            }
+            throw e;
+          }
+          
+          // Sleep and retry finding root region.
+          try {
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("Root region location changed. Sleeping.");
+            }
+            Thread.sleep(getPauseTime(tries));
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("Wake. Retry finding root region.");
+            }
+          } catch (InterruptedException iex) {
+            // continue
+          }
+        }
+        
+        rootRegionAddress = null;
+      }
+      
+      // if the address is null by this point, then the retries have failed,
+      // and we're sort of sunk
+      if (rootRegionAddress == null) {
+        throw new NoServerForRegionException(
+          "unable to locate root region server");
+      }
+      
+      // return the region location
+      return new HRegionLocation(
+        HRegionInfo.ROOT_REGIONINFO, rootRegionAddress);
+    }
+
+    /** {@inheritDoc} */
+    public <T> T getRegionServerWithRetries(ServerCallable<T> callable) 
+    throws IOException, RuntimeException {
+      getMaster();
+      List<Throwable> exceptions = new ArrayList<Throwable>();
+      for(int tries = 0; tries < numRetries; tries++) {
+        try {
+          callable.instantiateServer(tries != 0);
+          return callable.call();
+        } catch (Throwable t) {
+          if (t instanceof UndeclaredThrowableException) {
+            t = t.getCause();
+          }
+          if (t instanceof RemoteException) {
+            t = RemoteExceptionHandler.decodeRemoteException((RemoteException) t);
+          }
+          if (t instanceof DoNotRetryIOException) {
+            throw (DoNotRetryIOException)t;
+          }
+          exceptions.add(t);
+          if (tries == numRetries - 1) {
+            throw new RetriesExhaustedException(callable.getServerName(),
+                callable.getRegionName(), callable.getRow(), tries, exceptions);
+          }
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("reloading table servers because: " + t.getMessage());
+          }
+        }
+        try {
+          Thread.sleep(getPauseTime(tries));
+        } catch (InterruptedException e) {
+          // continue
+        }
+      }
+      return null;    
+    }
+  }
+}