/** * 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.procedure2; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.classification.InterfaceAudience; @InterfaceAudience.Private public abstract class AbstractProcedureScheduler implements ProcedureScheduler { private static final Log LOG = LogFactory.getLog(AbstractProcedureScheduler.class); private final ReentrantLock schedLock = new ReentrantLock(); private final Condition schedWaitCond = schedLock.newCondition(); private boolean running = false; // TODO: metrics private long pollCalls = 0; private long nullPollCalls = 0; @Override public void start() { schedLock(); try { running = true; } finally { schedUnlock(); } } @Override public void stop() { schedLock(); try { running = false; schedWaitCond.signalAll(); } finally { schedUnlock(); } } @Override public void signalAll() { schedLock(); try { schedWaitCond.signalAll(); } finally { schedUnlock(); } } // ========================================================================== // Add related // ========================================================================== /** * Add the procedure to the queue. * NOTE: this method is called with the sched lock held. * @param procedure the Procedure to add * @param addFront true if the item should be added to the front of the queue */ protected abstract void enqueue(Procedure procedure, boolean addFront); public void addFront(final Procedure procedure) { push(procedure, true, true); } public void addBack(final Procedure procedure) { push(procedure, false, true); } protected void push(final Procedure procedure, final boolean addFront, final boolean notify) { schedLock.lock(); try { enqueue(procedure, addFront); if (notify) { schedWaitCond.signal(); } } finally { schedLock.unlock(); } } // ========================================================================== // Poll related // ========================================================================== /** * Fetch one Procedure from the queue * NOTE: this method is called with the sched lock held. * @return the Procedure to execute, or null if nothing is available. */ protected abstract Procedure dequeue(); @Override public Procedure poll() { return poll(-1); } @Override public Procedure poll(long timeout, TimeUnit unit) { return poll(unit.toNanos(timeout)); } @edu.umd.cs.findbugs.annotations.SuppressWarnings("WA_AWAIT_NOT_IN_LOOP") public Procedure poll(final long nanos) { schedLock(); try { if (!running) { LOG.debug("the scheduler is not running"); return null; } if (!queueHasRunnables()) { // WA_AWAIT_NOT_IN_LOOP: we are not in a loop because we want the caller // to take decisions after a wake/interruption. if (nanos < 0) { schedWaitCond.await(); } else { schedWaitCond.awaitNanos(nanos); } if (!queueHasRunnables()) { nullPollCalls++; return null; } } final Procedure pollResult = dequeue(); pollCalls++; nullPollCalls += (pollResult == null) ? 1 : 0; return pollResult; } catch (InterruptedException e) { Thread.currentThread().interrupt(); nullPollCalls++; return null; } finally { schedUnlock(); } } // ========================================================================== // Utils // ========================================================================== /** * Returns the number of elements in this queue. * NOTE: this method is called with the sched lock held. * @return the number of elements in this queue. */ protected abstract int queueSize(); /** * Returns true if there are procedures available to process. * NOTE: this method is called with the sched lock held. * @return true if there are procedures available to process, otherwise false. */ protected abstract boolean queueHasRunnables(); @Override public int size() { schedLock(); try { return queueSize(); } finally { schedUnlock(); } } @Override public boolean hasRunnables() { schedLock(); try { return queueHasRunnables(); } finally { schedUnlock(); } } // ============================================================================ // TODO: Metrics // ============================================================================ public long getPollCalls() { return pollCalls; } public long getNullPollCalls() { return nullPollCalls; } // ========================================================================== // Procedure Events // ========================================================================== @Override public boolean waitEvent(final ProcedureEvent event, final Procedure procedure) { synchronized (event) { if (event.isReady()) { return false; } waitProcedure(event.getSuspendedProcedures(), procedure); return true; } } @Override public void suspendEvent(final ProcedureEvent event) { final boolean isTraceEnabled = LOG.isTraceEnabled(); synchronized (event) { event.setReady(false); if (isTraceEnabled) { LOG.trace("Suspend event " + event); } } } @Override public void wakeEvent(final ProcedureEvent event) { wakeEvents(1, event); } @Override public void wakeEvents(final int count, final ProcedureEvent... events) { final boolean isTraceEnabled = LOG.isTraceEnabled(); schedLock(); try { int waitingCount = 0; for (int i = 0; i < count; ++i) { final ProcedureEvent event = events[i]; synchronized (event) { event.setReady(true); if (isTraceEnabled) { LOG.trace("Wake event " + event); } waitingCount += wakeWaitingProcedures(event.getSuspendedProcedures()); } } wakePollIfNeeded(waitingCount); } finally { schedUnlock(); } } /** * Wakes up given waiting procedures by pushing them back into scheduler queues. * @return size of given {@code waitQueue}. */ protected int wakeWaitingProcedures(final ProcedureDeque waitQueue) { int count = waitQueue.size(); // wakeProcedure adds to the front of queue, so we start from last in the // waitQueue' queue, so that the procedure which was added first goes in the front for // the scheduler queue. while (!waitQueue.isEmpty()) { wakeProcedure(waitQueue.removeLast()); } return count; } protected void waitProcedure(final ProcedureDeque waitQueue, final Procedure proc) { waitQueue.addLast(proc); } protected void wakeProcedure(final Procedure procedure) { push(procedure, /* addFront= */ true, /* notify= */false); } // ========================================================================== // Internal helpers // ========================================================================== protected void schedLock() { schedLock.lock(); } protected void schedUnlock() { schedLock.unlock(); } protected void wakePollIfNeeded(final int waitingCount) { if (waitingCount <= 0) return; if (waitingCount == 1) { schedWaitCond.signal(); } else { schedWaitCond.signalAll(); } } }