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

org.apache.hadoop.hbase.master.assignment.RegionStateNodeLock Maven / Gradle / Ivy

/*
 * 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.master.assignment;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureFutureUtil;
import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
import org.apache.yetus.audience.InterfaceAudience;

/**
 * A lock implementation which supports unlock by another thread.
 * 

* This is because we need to hold region state node lock while updating region state to meta(for * keeping consistency), so it is better to yield the procedure to release the procedure worker. But * after waking up the procedure, we may use another procedure worker to execute the procedure, * which means we need to unlock by another thread. *

* For locking by procedure, we will also suspend the procedure if the lock is not ready, and * schedule it again when lock is ready. This is very important to not block the PEWorker as we may * hold the lock when updating meta, which could take a lot of time. *

* Please see HBASE-28196 for more details. */ @InterfaceAudience.Private class RegionStateNodeLock { // for better logging message private final RegionInfo regionInfo; private final Lock lock = new ReentrantLock(); private final Queue waitingQueue = new ArrayDeque<>(); private Object owner; private int count; /** * This is for abstraction the common lock/unlock logic for both Thread and Procedure. */ private interface QueueEntry { /** * A thread, or a procedure. */ Object getOwner(); /** * Called when we can not hold the lock and should wait. For thread, you should wait on a * condition, and for procedure, a ProcedureSuspendedException should be thrown. */ void await() throws ProcedureSuspendedException; /** * Called when it is your turn to get the lock. For thread, just delegate the call to * condition's signal method, and for procedure, you should call the {@code wakeUp} action, to * add the procedure back to procedure scheduler. */ void signal(); } RegionStateNodeLock(RegionInfo regionInfo) { this.regionInfo = regionInfo; } private void lock0(QueueEntry entry) throws ProcedureSuspendedException { lock.lock(); try { for (;;) { if (owner == null) { owner = entry.getOwner(); count = 1; return; } if (owner == entry.getOwner()) { count++; return; } waitingQueue.add(entry); entry.await(); } } finally { lock.unlock(); } } private boolean tryLock0(Object lockBy) { if (!lock.tryLock()) { return false; } try { if (owner == null) { owner = lockBy; count = 1; return true; } if (owner == lockBy) { count++; return true; } return false; } finally { lock.unlock(); } } private void unlock0(Object unlockBy) { lock.lock(); try { if (owner == null) { throw new IllegalMonitorStateException("RegionStateNode " + regionInfo + " is not locked"); } if (owner != unlockBy) { throw new IllegalMonitorStateException("RegionStateNode " + regionInfo + " is locked by " + owner + ", can not be unlocked by " + unlockBy); } count--; if (count == 0) { owner = null; QueueEntry entry = waitingQueue.poll(); if (entry != null) { entry.signal(); } } } finally { lock.unlock(); } } /** * Normal lock, will set the current thread as owner. Typically you should use try...finally to * call unlock in the finally block. */ void lock() { Thread currentThread = Thread.currentThread(); try { lock0(new QueueEntry() { private Condition cond; @Override public void signal() { cond.signal(); } @Override public Object getOwner() { return currentThread; } @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "WA_AWAIT_NOT_IN_LOOP", justification = "Loop is in the caller method") @Override public void await() { if (cond == null) { cond = lock.newCondition(); } cond.awaitUninterruptibly(); } }); } catch (ProcedureSuspendedException e) { // should not happen throw new AssertionError(e); } } /** * Normal tryLock, will set the current thread as owner. Typically you should use try...finally to * call unlock in the finally block. */ boolean tryLock() { return tryLock0(Thread.currentThread()); } /** * Normal unLock, will use the current thread as owner. Typically you should use try...finally to * call unlock in the finally block. */ void unlock() { unlock0(Thread.currentThread()); } /** * Lock by a procedure. You can release the lock in another thread. *

* When the procedure can not get the lock immediately, a ProcedureSuspendedException will be * thrown to suspend the procedure. And when we want to wake up a procedure, we will call the * {@code wakeUp} action. Usually in the {@code wakeUp} action you should add the procedure back * to procedure scheduler. */ void lock(Procedure proc, Runnable wakeUp) throws ProcedureSuspendedException { lock0(new QueueEntry() { @Override public Object getOwner() { return proc; } @Override public void await() throws ProcedureSuspendedException { ProcedureFutureUtil.suspend(proc); } @Override public void signal() { // Here we need to set the owner to the procedure directly. // For thread, we just block inside the lock0 method, so after signal we will continue and // get the lock // For procedure, the waking up here is actually a reschedule of the procedure to // ProcedureExecutor, so here we need to set the owner first, and it is the procedure's duty // to make sure that it has already hold the lock so do not need to call lock again, usually // this should be done by calling isLockedBy method below assert owner == null; assert count == 0; owner = proc; count = 1; wakeUp.run(); } }); } /** * TryLock by a procedure. You can release the lock in another thread. */ boolean tryLock(Procedure proc) { return tryLock0(proc); } /** * Unlock by a procedure. You do not need to call this method in the same thread with lock. */ void unlock(Procedure proc) { unlock0(proc); } /** * Check whether the lock is locked by someone. */ boolean isLocked() { lock.lock(); try { return owner != null; } finally { lock.unlock(); } } /** * Check whether the lock is locked by the given {@code lockBy}. */ boolean isLockedBy(Object lockBy) { lock.lock(); try { return owner == lockBy; } finally { lock.unlock(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy