All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.master.procedure.MasterProcedureScheduler Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/**
 * 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,
 * WITHOUTKey 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.master.procedure;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.master.locking.LockProcedure;
import org.apache.hadoop.hbase.master.procedure.TableProcedureInterface.TableOperationType;
import org.apache.hadoop.hbase.procedure2.AbstractProcedureScheduler;
import org.apache.hadoop.hbase.procedure2.LockAndQueue;
import org.apache.hadoop.hbase.procedure2.LockStatus;
import org.apache.hadoop.hbase.procedure2.LockType;
import org.apache.hadoop.hbase.procedure2.LockedResource;
import org.apache.hadoop.hbase.procedure2.LockedResourceType;
import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureDeque;
import org.apache.hadoop.hbase.util.AvlUtil.AvlIterableList;
import org.apache.hadoop.hbase.util.AvlUtil.AvlKeyComparator;
import org.apache.hadoop.hbase.util.AvlUtil.AvlLinkedNode;
import org.apache.hadoop.hbase.util.AvlUtil.AvlTree;
import org.apache.hadoop.hbase.util.AvlUtil.AvlTreeIterator;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;

/**
 * ProcedureScheduler for the Master Procedures.
 * This ProcedureScheduler tries to provide to the ProcedureExecutor procedures
 * that can be executed without having to wait on a lock.
 * Most of the master operations can be executed concurrently, if they
 * are operating on different tables (e.g. two create table procedures can be performed
 * at the same time) or against two different servers; say two servers that crashed at
 * about the same time.
 *
 * 

Each procedure should implement an Interface providing information for this queue. * For example table related procedures should implement TableProcedureInterface. * Each procedure will be pushed in its own queue, and based on the operation type * we may make smarter decisions: e.g. we can abort all the operations preceding * a delete table, or similar. * *

Concurrency control

* Concurrent access to member variables (tableRunQueue, serverRunQueue, locking, tableMap, * serverBuckets) is controlled by schedLock(). This mainly includes:
*
    *
  • * {@link #push(Procedure, boolean, boolean)}: A push will add a Queue back to run-queue * when: *
      *
    1. Queue was empty before push (so must have been out of run-queue)
    2. *
    3. Child procedure is added (which means parent procedure holds exclusive lock, and it * must have moved Queue out of run-queue)
    4. *
    *
  • *
  • * {@link #poll(long)}: A poll will remove a Queue from run-queue when: *
      *
    1. Queue becomes empty after poll
    2. *
    3. Exclusive lock is requested by polled procedure and lock is available (returns the * procedure)
    4. *
    5. Exclusive lock is requested but lock is not available (returns null)
    6. *
    7. Polled procedure is child of parent holding exclusive lock and the next procedure is * not a child
    8. *
    *
  • *
  • * Namespace/table/region locks: Queue is added back to run-queue when lock being released is: *
      *
    1. Exclusive lock
    2. *
    3. Last shared lock (in case queue was removed because next procedure in queue required * exclusive lock)
    4. *
    *
  • *
*/ @InterfaceAudience.Private public class MasterProcedureScheduler extends AbstractProcedureScheduler { private static final Logger LOG = LoggerFactory.getLogger(MasterProcedureScheduler.class); private final static ServerQueueKeyComparator SERVER_QUEUE_KEY_COMPARATOR = new ServerQueueKeyComparator(); private final static TableQueueKeyComparator TABLE_QUEUE_KEY_COMPARATOR = new TableQueueKeyComparator(); private final FairQueue serverRunQueue = new FairQueue<>(); private final FairQueue tableRunQueue = new FairQueue<>(); private final ServerQueue[] serverBuckets = new ServerQueue[128]; private TableQueue tableMap = null; private final SchemaLocking locking = new SchemaLocking(); /** * Table priority is used when scheduling procedures from {@link #tableRunQueue}. A TableQueue * with priority 2 will get its procedures scheduled at twice the rate as compared to * TableQueue with priority 1. This should be enough to ensure system/meta get assigned out * before user-space tables. HBASE-18109 is where we conclude what is here is good enough. * Lets open new issue if we find it not enough. */ private static class TablePriorities { final int metaTablePriority; final int userTablePriority; final int sysTablePriority; TablePriorities(Configuration conf) { metaTablePriority = conf.getInt("hbase.master.procedure.queue.meta.table.priority", 3); sysTablePriority = conf.getInt("hbase.master.procedure.queue.system.table.priority", 2); userTablePriority = conf.getInt("hbase.master.procedure.queue.user.table.priority", 1); } int getPriority(TableName tableName) { if (tableName.equals(TableName.META_TABLE_NAME)) { return metaTablePriority; } else if (tableName.isSystemTable()) { return sysTablePriority; } return userTablePriority; } } private final TablePriorities tablePriorities; public MasterProcedureScheduler(final Configuration conf) { tablePriorities = new TablePriorities(conf); } @Override public void yield(final Procedure proc) { push(proc, isTableProcedure(proc), true); } @Override protected void enqueue(final Procedure proc, final boolean addFront) { if (isTableProcedure(proc)) { doAdd(tableRunQueue, getTableQueue(getTableName(proc)), proc, addFront); } else if (isServerProcedure(proc)) { doAdd(serverRunQueue, getServerQueue(getServerName(proc)), proc, addFront); } else { // TODO: at the moment we only have Table and Server procedures // if you are implementing a non-table/non-server procedure, you have two options: create // a group for all the non-table/non-server procedures or try to find a key for your // non-table/non-server procedures and implement something similar to the TableRunQueue. throw new UnsupportedOperationException( "RQs for non-table/non-server procedures are not implemented yet: " + proc); } } private > void doAdd(final FairQueue fairq, final Queue queue, final Procedure proc, final boolean addFront) { queue.add(proc, addFront); if (!queue.getLockStatus().hasExclusiveLock() || queue.getLockStatus().isLockOwner(proc.getProcId())) { // if the queue was not remove for an xlock execution // or the proc is the lock owner, put the queue back into execution addToRunQueue(fairq, queue); } else if (queue.getLockStatus().hasParentLock(proc)) { assert addFront : "expected to add a child in the front"; // our (proc) parent has the xlock, // so the queue is not in the fairq (run-queue) // add it back to let the child run (inherit the lock) addToRunQueue(fairq, queue); } } @Override protected boolean queueHasRunnables() { return tableRunQueue.hasRunnables() || serverRunQueue.hasRunnables(); } @Override protected Procedure dequeue() { // For now, let server handling have precedence over table handling; presumption is that it // is more important handling crashed servers than it is running the // enabling/disabling tables, etc. Procedure pollResult = doPoll(serverRunQueue); if (pollResult == null) { pollResult = doPoll(tableRunQueue); } return pollResult; } private > Procedure doPoll(final FairQueue fairq) { final Queue rq = fairq.poll(); if (rq == null || !rq.isAvailable()) { return null; } final Procedure pollResult = rq.peek(); if (pollResult == null) { return null; } final boolean xlockReq = rq.requireExclusiveLock(pollResult); if (xlockReq && rq.getLockStatus().isLocked() && !rq.getLockStatus().hasLockAccess(pollResult)) { // someone is already holding the lock (e.g. shared lock). avoid a yield removeFromRunQueue(fairq, rq); return null; } rq.poll(); if (rq.isEmpty() || xlockReq) { removeFromRunQueue(fairq, rq); } else if (rq.getLockStatus().hasParentLock(pollResult)) { // if the rq is in the fairq because of runnable child // check if the next procedure is still a child. // if not, remove the rq from the fairq and go back to the xlock state Procedure nextProc = rq.peek(); if (nextProc != null && !Procedure.haveSameParent(nextProc, pollResult)) { removeFromRunQueue(fairq, rq); } } return pollResult; } private LockedResource createLockedResource(LockedResourceType resourceType, String resourceName, LockAndQueue queue) { LockType lockType; Procedure exclusiveLockOwnerProcedure; int sharedLockCount; if (queue.hasExclusiveLock()) { lockType = LockType.EXCLUSIVE; exclusiveLockOwnerProcedure = queue.getExclusiveLockOwnerProcedure(); sharedLockCount = 0; } else { lockType = LockType.SHARED; exclusiveLockOwnerProcedure = null; sharedLockCount = queue.getSharedLockCount(); } List> waitingProcedures = new ArrayList<>(); for (Procedure procedure : queue) { if (!(procedure instanceof LockProcedure)) { continue; } waitingProcedures.add(procedure); } return new LockedResource(resourceType, resourceName, lockType, exclusiveLockOwnerProcedure, sharedLockCount, waitingProcedures); } @Override public List getLocks() { schedLock(); try { List lockedResources = new ArrayList<>(); for (Entry entry : locking.serverLocks .entrySet()) { String serverName = entry.getKey().getServerName(); LockAndQueue queue = entry.getValue(); if (queue.isLocked()) { LockedResource lockedResource = createLockedResource(LockedResourceType.SERVER, serverName, queue); lockedResources.add(lockedResource); } } for (Entry entry : locking.namespaceLocks .entrySet()) { String namespaceName = entry.getKey(); LockAndQueue queue = entry.getValue(); if (queue.isLocked()) { LockedResource lockedResource = createLockedResource(LockedResourceType.NAMESPACE, namespaceName, queue); lockedResources.add(lockedResource); } } for (Entry entry : locking.tableLocks .entrySet()) { String tableName = entry.getKey().getNameAsString(); LockAndQueue queue = entry.getValue(); if (queue.isLocked()) { LockedResource lockedResource = createLockedResource(LockedResourceType.TABLE, tableName, queue); lockedResources.add(lockedResource); } } for (Entry entry : locking.regionLocks.entrySet()) { String regionName = entry.getKey(); LockAndQueue queue = entry.getValue(); if (queue.isLocked()) { LockedResource lockedResource = createLockedResource(LockedResourceType.REGION, regionName, queue); lockedResources.add(lockedResource); } } return lockedResources; } finally { schedUnlock(); } } @Override public LockedResource getLockResource(LockedResourceType resourceType, String resourceName) { LockAndQueue queue = null; schedLock(); try { switch (resourceType) { case SERVER: queue = locking.serverLocks.get(ServerName.valueOf(resourceName)); break; case NAMESPACE: queue = locking.namespaceLocks.get(resourceName); break; case TABLE: queue = locking.tableLocks.get(TableName.valueOf(resourceName)); break; case REGION: queue = locking.regionLocks.get(resourceName); break; } return queue != null ? createLockedResource(resourceType, resourceName, queue) : null; } finally { schedUnlock(); } } @Override public void clear() { schedLock(); try { clearQueue(); locking.clear(); } finally { schedUnlock(); } } protected void clearQueue() { // Remove Servers for (int i = 0; i < serverBuckets.length; ++i) { clear(serverBuckets[i], serverRunQueue, SERVER_QUEUE_KEY_COMPARATOR); serverBuckets[i] = null; } // Remove Tables clear(tableMap, tableRunQueue, TABLE_QUEUE_KEY_COMPARATOR); tableMap = null; assert size() == 0 : "expected queue size to be 0, got " + size(); } private , TNode extends Queue> void clear(TNode treeMap, final FairQueue fairq, final AvlKeyComparator comparator) { while (treeMap != null) { Queue node = AvlTree.getFirst(treeMap); treeMap = AvlTree.remove(treeMap, node.getKey(), comparator); if (fairq != null) removeFromRunQueue(fairq, node); } } @Override protected int queueSize() { int count = 0; // Server queues final AvlTreeIterator serverIter = new AvlTreeIterator<>(); for (int i = 0; i < serverBuckets.length; ++i) { serverIter.seekFirst(serverBuckets[i]); while (serverIter.hasNext()) { count += serverIter.next().size(); } } // Table queues final AvlTreeIterator tableIter = new AvlTreeIterator<>(tableMap); while (tableIter.hasNext()) { count += tableIter.next().size(); } return count; } @Override public void completionCleanup(final Procedure proc) { if (proc instanceof TableProcedureInterface) { TableProcedureInterface iProcTable = (TableProcedureInterface)proc; boolean tableDeleted; if (proc.hasException()) { Exception procEx = proc.getException().unwrapRemoteException(); if (iProcTable.getTableOperationType() == TableOperationType.CREATE) { // create failed because the table already exist tableDeleted = !(procEx instanceof TableExistsException); } else { // the operation failed because the table does not exist tableDeleted = (procEx instanceof TableNotFoundException); } } else { // the table was deleted tableDeleted = (iProcTable.getTableOperationType() == TableOperationType.DELETE); } if (tableDeleted) { markTableAsDeleted(iProcTable.getTableName(), proc); return; } } else { // No cleanup for ServerProcedureInterface types, yet. return; } } private static > void addToRunQueue(FairQueue fairq, Queue queue) { if (!AvlIterableList.isLinked(queue) && !queue.isEmpty()) { fairq.add(queue); } } private static > void removeFromRunQueue( FairQueue fairq, Queue queue) { if (AvlIterableList.isLinked(queue)) { fairq.remove(queue); } } // ============================================================================ // Table Queue Lookup Helpers // ============================================================================ private TableQueue getTableQueue(TableName tableName) { TableQueue node = AvlTree.get(tableMap, tableName, TABLE_QUEUE_KEY_COMPARATOR); if (node != null) return node; node = new TableQueue(tableName, tablePriorities.getPriority(tableName), locking.getTableLock(tableName), locking.getNamespaceLock(tableName.getNamespaceAsString())); tableMap = AvlTree.insert(tableMap, node); return node; } private void removeTableQueue(TableName tableName) { tableMap = AvlTree.remove(tableMap, tableName, TABLE_QUEUE_KEY_COMPARATOR); locking.removeTableLock(tableName); } private static boolean isTableProcedure(Procedure proc) { return proc instanceof TableProcedureInterface; } private static TableName getTableName(Procedure proc) { return ((TableProcedureInterface)proc).getTableName(); } // ============================================================================ // Server Queue Lookup Helpers // ============================================================================ private ServerQueue getServerQueue(ServerName serverName) { final int index = getBucketIndex(serverBuckets, serverName.hashCode()); ServerQueue node = AvlTree.get(serverBuckets[index], serverName, SERVER_QUEUE_KEY_COMPARATOR); if (node != null) return node; node = new ServerQueue(serverName, locking.getServerLock(serverName)); serverBuckets[index] = AvlTree.insert(serverBuckets[index], node); return node; } private static int getBucketIndex(Object[] buckets, int hashCode) { return Math.abs(hashCode) % buckets.length; } private static boolean isServerProcedure(Procedure proc) { return proc instanceof ServerProcedureInterface; } private static ServerName getServerName(Procedure proc) { return ((ServerProcedureInterface)proc).getServerName(); } // ============================================================================ // Table and Server Queue Implementation // ============================================================================ private static class ServerQueueKeyComparator implements AvlKeyComparator { @Override public int compareKey(ServerQueue node, Object key) { return node.compareKey((ServerName)key); } } public static class ServerQueue extends Queue { public ServerQueue(ServerName serverName, LockStatus serverLock) { super(serverName, serverLock); } @Override public boolean requireExclusiveLock(Procedure proc) { ServerProcedureInterface spi = (ServerProcedureInterface)proc; switch (spi.getServerOperationType()) { case CRASH_HANDLER: return true; default: break; } throw new UnsupportedOperationException("unexpected type " + spi.getServerOperationType()); } } private static class TableQueueKeyComparator implements AvlKeyComparator { @Override public int compareKey(TableQueue node, Object key) { return node.compareKey((TableName)key); } } public static class TableQueue extends Queue { private final LockStatus namespaceLockStatus; public TableQueue(TableName tableName, int priority, LockStatus tableLock, LockStatus namespaceLockStatus) { super(tableName, priority, tableLock); this.namespaceLockStatus = namespaceLockStatus; } @Override public boolean isAvailable() { // if there are no items in the queue, or the namespace is locked. // we can't execute operation on this table if (isEmpty() || namespaceLockStatus.hasExclusiveLock()) { return false; } if (getLockStatus().hasExclusiveLock()) { // if we have an exclusive lock already taken // only child of the lock owner can be executed final Procedure nextProc = peek(); return nextProc != null && getLockStatus().hasLockAccess(nextProc); } // no xlock return true; } @Override public boolean requireExclusiveLock(Procedure proc) { return requireTableExclusiveLock((TableProcedureInterface)proc); } } // ============================================================================ // Table Locking Helpers // ============================================================================ /** * @param proc must not be null */ private static boolean requireTableExclusiveLock(TableProcedureInterface proc) { switch (proc.getTableOperationType()) { case CREATE: case DELETE: case DISABLE: case ENABLE: return true; case EDIT: // we allow concurrent edit on the NS table return !proc.getTableName().equals(TableName.NAMESPACE_TABLE_NAME); case READ: return false; // region operations are using the shared-lock on the table // and then they will grab an xlock on the region. case REGION_SPLIT: case REGION_MERGE: case REGION_ASSIGN: case REGION_UNASSIGN: case REGION_EDIT: case REGION_GC: case MERGED_REGIONS_GC: return false; default: break; } throw new UnsupportedOperationException("unexpected type " + proc.getTableOperationType()); } /** * Get lock info for a resource of specified type and name and log details */ protected void logLockedResource(LockedResourceType resourceType, String resourceName) { if (!LOG.isDebugEnabled()) { return; } LockedResource lockedResource = getLockResource(resourceType, resourceName); if (lockedResource != null) { String msg = resourceType.toString() + " '" + resourceName + "', shared lock count=" + lockedResource.getSharedLockCount(); Procedure proc = lockedResource.getExclusiveLockOwnerProcedure(); if (proc != null) { msg += ", exclusively locked by procId=" + proc.getProcId(); } LOG.debug(msg); } } /** * Suspend the procedure if the specified table is already locked. * Other operations in the table-queue will be executed after the lock is released. * @param procedure the procedure trying to acquire the lock * @param table Table to lock * @return true if the procedure has to wait for the table to be available */ public boolean waitTableExclusiveLock(final Procedure procedure, final TableName table) { schedLock(); try { final String namespace = table.getNamespaceAsString(); final LockAndQueue namespaceLock = locking.getNamespaceLock(namespace); final LockAndQueue tableLock = locking.getTableLock(table); if (!namespaceLock.trySharedLock()) { waitProcedure(namespaceLock, procedure); logLockedResource(LockedResourceType.NAMESPACE, namespace); return true; } if (!tableLock.tryExclusiveLock(procedure)) { namespaceLock.releaseSharedLock(); waitProcedure(tableLock, procedure); logLockedResource(LockedResourceType.TABLE, table.getNameAsString()); return true; } removeFromRunQueue(tableRunQueue, getTableQueue(table)); return false; } finally { schedUnlock(); } } /** * Wake the procedures waiting for the specified table * @param procedure the procedure releasing the lock * @param table the name of the table that has the exclusive lock */ public void wakeTableExclusiveLock(final Procedure procedure, final TableName table) { schedLock(); try { final LockAndQueue namespaceLock = locking.getNamespaceLock(table.getNamespaceAsString()); final LockAndQueue tableLock = locking.getTableLock(table); int waitingCount = 0; if (!tableLock.hasParentLock(procedure)) { tableLock.releaseExclusiveLock(procedure); waitingCount += wakeWaitingProcedures(tableLock); } if (namespaceLock.releaseSharedLock()) { waitingCount += wakeWaitingProcedures(namespaceLock); } addToRunQueue(tableRunQueue, getTableQueue(table)); wakePollIfNeeded(waitingCount); } finally { schedUnlock(); } } /** * Suspend the procedure if the specified table is already locked. * other "read" operations in the table-queue may be executed concurrently, * @param procedure the procedure trying to acquire the lock * @param table Table to lock * @return true if the procedure has to wait for the table to be available */ public boolean waitTableSharedLock(final Procedure procedure, final TableName table) { return waitTableQueueSharedLock(procedure, table) == null; } private TableQueue waitTableQueueSharedLock(final Procedure procedure, final TableName table) { schedLock(); try { final LockAndQueue namespaceLock = locking.getNamespaceLock(table.getNamespaceAsString()); final LockAndQueue tableLock = locking.getTableLock(table); if (!namespaceLock.trySharedLock()) { waitProcedure(namespaceLock, procedure); return null; } if (!tableLock.trySharedLock()) { namespaceLock.releaseSharedLock(); waitProcedure(tableLock, procedure); return null; } return getTableQueue(table); } finally { schedUnlock(); } } /** * Wake the procedures waiting for the specified table * @param procedure the procedure releasing the lock * @param table the name of the table that has the shared lock */ public void wakeTableSharedLock(final Procedure procedure, final TableName table) { schedLock(); try { final LockAndQueue namespaceLock = locking.getNamespaceLock(table.getNamespaceAsString()); final LockAndQueue tableLock = locking.getTableLock(table); int waitingCount = 0; if (tableLock.releaseSharedLock()) { addToRunQueue(tableRunQueue, getTableQueue(table)); waitingCount += wakeWaitingProcedures(tableLock); } if (namespaceLock.releaseSharedLock()) { waitingCount += wakeWaitingProcedures(namespaceLock); } wakePollIfNeeded(waitingCount); } finally { schedUnlock(); } } /** * Tries to remove the queue and the table-lock of the specified table. * If there are new operations pending (e.g. a new create), * the remove will not be performed. * @param table the name of the table that should be marked as deleted * @param procedure the procedure that is removing the table * @return true if deletion succeeded, false otherwise meaning that there are * other new operations pending for that table (e.g. a new create). */ @VisibleForTesting protected boolean markTableAsDeleted(final TableName table, final Procedure procedure) { schedLock(); try { final TableQueue queue = getTableQueue(table); final LockAndQueue tableLock = locking.getTableLock(table); if (queue == null) return true; if (queue.isEmpty() && tableLock.tryExclusiveLock(procedure)) { // remove the table from the run-queue and the map if (AvlIterableList.isLinked(queue)) { tableRunQueue.remove(queue); } removeTableQueue(table); } else { // TODO: If there are no create, we can drop all the other ops return false; } } finally { schedUnlock(); } return true; } // ============================================================================ // Region Locking Helpers // ============================================================================ /** * Suspend the procedure if the specified region is already locked. * @param procedure the procedure trying to acquire the lock on the region * @param regionInfo the region we are trying to lock * @return true if the procedure has to wait for the regions to be available */ public boolean waitRegion(final Procedure procedure, final RegionInfo regionInfo) { return waitRegions(procedure, regionInfo.getTable(), regionInfo); } /** * Suspend the procedure if the specified set of regions are already locked. * @param procedure the procedure trying to acquire the lock on the regions * @param table the table name of the regions we are trying to lock * @param regionInfo the list of regions we are trying to lock * @return true if the procedure has to wait for the regions to be available */ public boolean waitRegions(final Procedure procedure, final TableName table, final RegionInfo... regionInfo) { Arrays.sort(regionInfo, RegionInfo.COMPARATOR); schedLock(); try { // If there is parent procedure, it would have already taken xlock, so no need to take // shared lock here. Otherwise, take shared lock. if (!procedure.hasParent() && waitTableQueueSharedLock(procedure, table) == null) { return true; } // acquire region xlocks or wait boolean hasLock = true; final LockAndQueue[] regionLocks = new LockAndQueue[regionInfo.length]; for (int i = 0; i < regionInfo.length; ++i) { LOG.info(procedure + " " + table + " " + regionInfo[i].getRegionNameAsString()); assert table != null; assert regionInfo[i] != null; assert regionInfo[i].getTable() != null; assert regionInfo[i].getTable().equals(table): regionInfo[i] + " " + procedure; assert i == 0 || regionInfo[i] != regionInfo[i - 1] : "duplicate region: " + regionInfo[i]; regionLocks[i] = locking.getRegionLock(regionInfo[i].getEncodedName()); if (!regionLocks[i].tryExclusiveLock(procedure)) { waitProcedure(regionLocks[i], procedure); hasLock = false; while (i-- > 0) { regionLocks[i].releaseExclusiveLock(procedure); } break; } } if (!hasLock && !procedure.hasParent()) { wakeTableSharedLock(procedure, table); } return !hasLock; } finally { schedUnlock(); } } /** * Wake the procedures waiting for the specified region * @param procedure the procedure that was holding the region * @param regionInfo the region the procedure was holding */ public void wakeRegion(final Procedure procedure, final RegionInfo regionInfo) { wakeRegions(procedure, regionInfo.getTable(), regionInfo); } /** * Wake the procedures waiting for the specified regions * @param procedure the procedure that was holding the regions * @param regionInfo the list of regions the procedure was holding */ public void wakeRegions(final Procedure procedure,final TableName table, final RegionInfo... regionInfo) { Arrays.sort(regionInfo, RegionInfo.COMPARATOR); schedLock(); try { int numProcs = 0; final Procedure[] nextProcs = new Procedure[regionInfo.length]; for (int i = 0; i < regionInfo.length; ++i) { assert regionInfo[i].getTable().equals(table); assert i == 0 || regionInfo[i] != regionInfo[i - 1] : "duplicate region: " + regionInfo[i]; LockAndQueue regionLock = locking.getRegionLock(regionInfo[i].getEncodedName()); if (regionLock.releaseExclusiveLock(procedure)) { if (!regionLock.isEmpty()) { // release one procedure at the time since regions has an xlock nextProcs[numProcs++] = regionLock.removeFirst(); } else { locking.removeRegionLock(regionInfo[i].getEncodedName()); } } } // awake procedures if any for (int i = numProcs - 1; i >= 0; --i) { wakeProcedure(nextProcs[i]); } wakePollIfNeeded(numProcs); if (!procedure.hasParent()) { // release the table shared-lock. // (if we have a parent, it is holding an xlock so we didn't take the shared-lock) wakeTableSharedLock(procedure, table); } } finally { schedUnlock(); } } // ============================================================================ // Namespace Locking Helpers // ============================================================================ /** * Suspend the procedure if the specified namespace is already locked. * @see #wakeNamespaceExclusiveLock(Procedure,String) * @param procedure the procedure trying to acquire the lock * @param namespace Namespace to lock * @return true if the procedure has to wait for the namespace to be available */ public boolean waitNamespaceExclusiveLock(final Procedure procedure, final String namespace) { schedLock(); try { final LockAndQueue systemNamespaceTableLock = locking.getTableLock(TableName.NAMESPACE_TABLE_NAME); if (!systemNamespaceTableLock.trySharedLock()) { waitProcedure(systemNamespaceTableLock, procedure); logLockedResource(LockedResourceType.TABLE, TableName.NAMESPACE_TABLE_NAME.getNameAsString()); return true; } final LockAndQueue namespaceLock = locking.getNamespaceLock(namespace); if (!namespaceLock.tryExclusiveLock(procedure)) { systemNamespaceTableLock.releaseSharedLock(); waitProcedure(namespaceLock, procedure); logLockedResource(LockedResourceType.NAMESPACE, namespace); return true; } return false; } finally { schedUnlock(); } } /** * Wake the procedures waiting for the specified namespace * @see #waitNamespaceExclusiveLock(Procedure,String) * @param procedure the procedure releasing the lock * @param namespace the namespace that has the exclusive lock */ public void wakeNamespaceExclusiveLock(final Procedure procedure, final String namespace) { schedLock(); try { final LockAndQueue namespaceLock = locking.getNamespaceLock(namespace); final LockAndQueue systemNamespaceTableLock = locking.getTableLock(TableName.NAMESPACE_TABLE_NAME); namespaceLock.releaseExclusiveLock(procedure); int waitingCount = 0; if (systemNamespaceTableLock.releaseSharedLock()) { addToRunQueue(tableRunQueue, getTableQueue(TableName.NAMESPACE_TABLE_NAME)); waitingCount += wakeWaitingProcedures(systemNamespaceTableLock); } waitingCount += wakeWaitingProcedures(namespaceLock); wakePollIfNeeded(waitingCount); } finally { schedUnlock(); } } // ============================================================================ // Server Locking Helpers // ============================================================================ /** * Try to acquire the exclusive lock on the specified server. * @see #wakeServerExclusiveLock(Procedure,ServerName) * @param procedure the procedure trying to acquire the lock * @param serverName Server to lock * @return true if the procedure has to wait for the server to be available */ public boolean waitServerExclusiveLock(final Procedure procedure, final ServerName serverName) { schedLock(); try { final LockAndQueue lock = locking.getServerLock(serverName); if (lock.tryExclusiveLock(procedure)) { removeFromRunQueue(serverRunQueue, getServerQueue(serverName)); return false; } waitProcedure(lock, procedure); logLockedResource(LockedResourceType.SERVER, serverName.getServerName()); return true; } finally { schedUnlock(); } } /** * Wake the procedures waiting for the specified server * @see #waitServerExclusiveLock(Procedure,ServerName) * @param procedure the procedure releasing the lock * @param serverName the server that has the exclusive lock */ public void wakeServerExclusiveLock(final Procedure procedure, final ServerName serverName) { schedLock(); try { final LockAndQueue lock = locking.getServerLock(serverName); lock.releaseExclusiveLock(procedure); addToRunQueue(serverRunQueue, getServerQueue(serverName)); int waitingCount = wakeWaitingProcedures(lock); wakePollIfNeeded(waitingCount); } finally { schedUnlock(); } } // ============================================================================ // Generic Helpers // ============================================================================ private static abstract class Queue> extends AvlLinkedNode> { /** * @param proc must not be null */ abstract boolean requireExclusiveLock(Procedure proc); private final TKey key; private final int priority; private final ProcedureDeque runnables = new ProcedureDeque(); // Reference to status of lock on entity this queue represents. private final LockStatus lockStatus; public Queue(TKey key, LockStatus lockStatus) { this(key, 1, lockStatus); } public Queue(TKey key, int priority, LockStatus lockStatus) { this.key = key; this.priority = priority; this.lockStatus = lockStatus; } protected TKey getKey() { return key; } protected int getPriority() { return priority; } protected LockStatus getLockStatus() { return lockStatus; } // This should go away when we have the new AM and its events // and we move xlock to the lock-event-queue. public boolean isAvailable() { return !lockStatus.hasExclusiveLock() && !isEmpty(); } // ====================================================================== // Functions to handle procedure queue // ====================================================================== public void add(final Procedure proc, final boolean addToFront) { if (addToFront) { runnables.addFirst(proc); } else { runnables.addLast(proc); } } public Procedure peek() { return runnables.peek(); } public Procedure poll() { return runnables.poll(); } public boolean isEmpty() { return runnables.isEmpty(); } public int size() { return runnables.size(); } // ====================================================================== // Generic Helpers // ====================================================================== public int compareKey(TKey cmpKey) { return key.compareTo(cmpKey); } @Override public int compareTo(Queue other) { return compareKey(other.key); } @Override public String toString() { return String.format("%s(%s, xlock=%s sharedLock=%s size=%s)", getClass().getSimpleName(), key, lockStatus.hasExclusiveLock() ? "true (" + lockStatus.getExclusiveLockProcIdOwner() + ")" : "false", lockStatus.getSharedLockCount(), size()); } } /** * Locks on namespaces, tables, and regions. * Since LockAndQueue implementation is NOT thread-safe, schedLock() guards all calls to these * locks. */ private static class SchemaLocking { final Map serverLocks = new HashMap<>(); final Map namespaceLocks = new HashMap<>(); final Map tableLocks = new HashMap<>(); // Single map for all regions irrespective of tables. Key is encoded region name. final Map regionLocks = new HashMap<>(); private LockAndQueue getLock(Map map, T key) { LockAndQueue lock = map.get(key); if (lock == null) { lock = new LockAndQueue(); map.put(key, lock); } return lock; } LockAndQueue getTableLock(TableName tableName) { return getLock(tableLocks, tableName); } LockAndQueue removeTableLock(TableName tableName) { return tableLocks.remove(tableName); } LockAndQueue getNamespaceLock(String namespace) { return getLock(namespaceLocks, namespace); } LockAndQueue getRegionLock(String encodedRegionName) { return getLock(regionLocks, encodedRegionName); } LockAndQueue removeRegionLock(String encodedRegionName) { return regionLocks.remove(encodedRegionName); } LockAndQueue getServerLock(ServerName serverName) { return getLock(serverLocks, serverName); } /** * Removes all locks by clearing the maps. * Used when procedure executor is stopped for failure and recovery testing. */ @VisibleForTesting void clear() { serverLocks.clear(); namespaceLocks.clear(); tableLocks.clear(); regionLocks.clear(); } @Override public String toString() { return "serverLocks=" + filterUnlocked(this.serverLocks) + ", namespaceLocks=" + filterUnlocked(this.namespaceLocks) + ", tableLocks=" + filterUnlocked(this.tableLocks) + ", regionLocks=" + filterUnlocked(this.regionLocks); } private String filterUnlocked(Map locks) { StringBuilder sb = new StringBuilder("{"); int initialLength = sb.length(); for (Map.Entry entry: locks.entrySet()) { if (!entry.getValue().isLocked()) continue; if (sb.length() > initialLength) sb.append(", "); sb.append("{"); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); sb.append("}"); } sb.append("}"); return sb.toString(); } } // ====================================================================== // Helper Data Structures // ====================================================================== private static class FairQueue> { private final int quantum; private Queue currentQueue = null; private Queue queueHead = null; private int currentQuantum = 0; private int size = 0; public FairQueue() { this(1); } public FairQueue(int quantum) { this.quantum = quantum; } public boolean hasRunnables() { return size > 0; } public void add(Queue queue) { queueHead = AvlIterableList.append(queueHead, queue); if (currentQueue == null) setNextQueue(queueHead); size++; } public void remove(Queue queue) { Queue nextQueue = AvlIterableList.readNext(queue); queueHead = AvlIterableList.remove(queueHead, queue); if (currentQueue == queue) { setNextQueue(queueHead != null ? nextQueue : null); } size--; } public Queue poll() { if (currentQuantum == 0) { if (!nextQueue()) { return null; // nothing here } currentQuantum = calculateQuantum(currentQueue) - 1; } else { currentQuantum--; } // This should go away when we have the new AM and its events if (!currentQueue.isAvailable()) { Queue lastQueue = currentQueue; do { if (!nextQueue()) return null; } while (currentQueue != lastQueue && !currentQueue.isAvailable()); currentQuantum = calculateQuantum(currentQueue) - 1; } return currentQueue; } private boolean nextQueue() { if (currentQueue == null) return false; currentQueue = AvlIterableList.readNext(currentQueue); return currentQueue != null; } private void setNextQueue(Queue queue) { currentQueue = queue; if (queue != null) { currentQuantum = calculateQuantum(currentQueue); } else { currentQuantum = 0; } } private int calculateQuantum(final Queue queue) { return Math.max(1, queue.getPriority() * quantum); // TODO } } /** * For debugging. Expensive. * @throws IOException */ @VisibleForTesting public String dumpLocks() throws IOException { schedLock(); try { // TODO: Refactor so we stream out locks for case when millions; i.e. take a PrintWriter return this.locking.toString(); } finally { schedUnlock(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy