![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.concurrent.NonBlockingLockManager Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Oct 3, 2007
*/
package com.bigdata.concurrent;
import static com.bigdata.concurrent.NonBlockingLockManager.RunState.Running;
import static com.bigdata.concurrent.NonBlockingLockManager.RunState.Shutdown;
import static com.bigdata.concurrent.NonBlockingLockManager.RunState.Starting;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.bigdata.cache.ConcurrentWeakValueCacheWithTimeout;
import com.bigdata.counters.CounterSet;
import com.bigdata.counters.Instrument;
import com.bigdata.journal.AbstractTask;
import com.bigdata.util.DaemonThreadFactory;
import com.bigdata.util.concurrent.ThreadPoolExecutorStatisticsTask;
import com.bigdata.util.concurrent.WriteTaskCounters;
/**
* This class coordinates a schedule among concurrent operations requiring
* exclusive access to shared resources. Whenever possible, the result is a
* concurrent schedule - that is, operations having non-overlapping lock
* requirements run concurrently while operations that have lock contentions are
* queued behind operations that currently have locks on the relevant resources.
* A {@link ResourceQueue} is created for each resource and used to block
* operations that are awaiting a lock. When locks are not being pre-declared, a
* {@link TxDag WAITS_FOR} graph is additionally used to detect deadlocks.
*
* This implementation uses a single {@link AcceptTask} thread to accept tasks,
* update the requests in the {@link ResourceQueue}s, and in general perform
* housekeeping for the internal state. Tasks submitted to this class ARE NOT
* bound to a worker thread until they are executed by the delegate
* {@link Executor}.
*
* @author Bryan Thompson
* @version $Id$
*
* @param R
* The type of the object that identifies a resource for the purposes
* of the locking system. This is typically the name of an index.
*
* @todo a fair option? What constraints or freedoms would it provide?
*
* @todo a {@link SynchronousQueue} for the {@link #acceptedTasks}?
*
* @todo a {@link SynchronousQueue} for the writeService workQueue?
*
* FIXME In order to support 2PL we need to decouple the {@link LockFutureTask}
* from the transaction with which it is associated. Otherwise each
* {@link #submit(Comparable[], Callable)} will look like a new transaction (2PL
* is impossible unless you can execute multiple tasks for the same
* transaction).
*
* Perhaps this would be easier if we did not delegate the task to an
* {@link Executor} since that does not really support the 2PL pattern.
*
* @todo Support escalation of operation priority based on time and scheduling
* of higher priority operations. the latter is done by queueing lock
* requests in front of pending requests for each resource on which an
* operation attempt to gain a lock. The former is just a dynamic
* adjustment of the position of the operation in the resource queue where
* it is awaiting a lock (an operation never awaits more than one lock at
* a time). This facility could be used to give priority to distributed
* transactions over local unisolated operations and to priviledge certain
* operations that have low latency requirements. This is not quite a
* "real-time" guarentee since the VM is not (normally) providing
* real-time guarentees and since we are not otherwise attempting to
* ensure anything except lower latency when compared to other operations
* awaiting their own locks.
*/
public class NonBlockingLockManager* T, */R extends Comparable> {
final protected static Logger log = Logger
.getLogger(NonBlockingLockManager.class);
final protected static boolean INFO = log.isInfoEnabled();
final protected static boolean DEBUG = log.isDebugEnabled();
/**
* Each resource that can be locked has an associated {@link ResourceQueue}.
*
* Note: This is a concurrent collection since new resources may be added
* while concurrent operations resolve resources to their queues. Stale
* {@link ResourceQueue}s are purged after they become only weakly
* reachable.
*
* @todo reconsider the timeout. it is set to one LBS period right now.
*/
final private ConcurrentWeakValueCacheWithTimeout>> resourceQueues = new ConcurrentWeakValueCacheWithTimeout>>(
1000/* nresources */, TimeUnit.SECONDS.toNanos(60));
/**
* Release all locks held by the {@link LockFutureTask} currently holding a
* lock on the specified resource.
*
* @param resource[]
* The declared locks for the task.
*
* FIXME This is an integration hack for {@link AbstractTask}.
* {@link AbstractTask} needs to be able to release the locks as soon as the
* work of an unisolated task is done (when it is waiting for a group
* commit) so that other tasks can gain access to the same indices and make
* it into the same group commit. It is using this method to obtain the
* {@link LockFutureTask} and then release its locks.
*
* {@link AbstractTask} really needs a refactor.
*/
public final void releaseLocksForTask(final R[] resource) {
if (resource == null)
throw new IllegalArgumentException();
if(resource.length == 0) {
// No declared locks.
return;
}
lock.lock();
try {
LockFutureTask extends Object> task = null;
for (R r : resource) {
final ResourceQueue> resourceQueue = resourceQueues
.get(r);
if (task == null) {
/*
* find the task by checking the resource queue for any of
* its declared locks.
*/
task = resourceQueue.queue.peek();
if (task == null)
throw new IllegalArgumentException(
"Task does not hold declared lock: " + r);
} else {
/*
* verify that the task holds the rest of its declared
* locks.
*/
if (task != resourceQueue.queue.peek()) {
throw new IllegalArgumentException(
"Task does not hold declared lock: " + r);
}
}
}
if(task == null) {
throw new AssertionError();
}
/*
* At this point we have verified that there is a task which holds
* all of the locks specified by the caller.
*/
/*
* The task will be running since that is the presumption for the
* context in which this method is invoked within AbstractTask.
*/
final boolean waiting = false;
// release the locks for the task.
releaseLocks(task, waiting);
} finally {
lock.unlock();
}
}
/**
* True iff locks MUST be predeclared by the operation - this is a special
* case of 2PL (two-phrase locking) that allows significant optimizations
* and avoids the possibility of deadlock altogether.
*/
final private boolean predeclareLocks;
/**
* When true, the resources in a lock request are sorted before the lock
* requests are made to the various resource queues. This option is ONLY
* turned off for testing purposes as it ALWAYS reduces the chance of
* deadlocks and eliminates it entirely when locks are also predeclared.
*/
final private boolean sortLockRequests;
/**
* Used to track dependencies among transactions.
*/
final protected TxDag waitsFor;
/**
* Tasks holding their locks are submitted to this service for execution.
*/
final protected Executor delegate;
/**
* Run states for the {@link NonBlockingLockManager}.
*
* @author Bryan Thompson
* @version $Id$
*/
static public enum RunState {
/**
* During startup. Tasks are NOT accepted.
*/
Starting(0),
/**
* While running (aka open). Tasks are accepted and submitted for
* execution once they hold their locks.
*/
Running(1),
/**
* When shutting down normally. New tasks are not accepted but
* {@link Future}s are still monitored for completion and waiting tasks
* will eventually be granted their locks and execute on the delegate.
*/
Shutdown(2),
/**
* When shutting down immediately. New tasks are not accepted, tasks
* waiting for their locks are cancelled (they will not execute) and
* {@link Future}s for running tasks are cancelled (they are
* interrupted).
*/
ShutdownNow(3),
/**
* When halted. New tasks are not accepted. No tasks are waiting. Any
* {@link Future}s were cancelled.
*/
Halted(4);
private RunState(int val) {
this.val = val;
}
final private int val;
public int value() {
return val;
}
public boolean isTransitionLegal(final RunState newval) {
if (this == Starting) {
if (newval == Running)
return true;
if (newval == Halted)
return true;
} else if (this == Running) {
if (newval == Shutdown)
return true;
if (newval == ShutdownNow)
return true;
} else if (this == Shutdown) {
if (newval == ShutdownNow)
return true;
if (newval == Halted)
return true;
} else if (this == ShutdownNow) {
if (newval == Halted)
return true;
}
return false;
}
}
/*
* counters
*/
synchronized public CounterSet getCounters() {
if (root == null) {
root = new CounterSet();
root.addCounter("naccepted", new Instrument() {
public void sample() {
setValue(counters.naccepted);
}
});
root.addCounter("nrejected", new Instrument() {
public void sample() {
setValue(counters.nrejected);
}
});
root.addCounter("nstarted", new Instrument() {
public void sample() {
setValue(counters.nstarted);
}
});
root.addCounter("nended", new Instrument() {
public void sample() {
setValue(counters.nended);
}
});
root.addCounter("ncancel", new Instrument() {
public void sample() {
setValue(counters.ncancel);
}
});
root.addCounter("nerror", new Instrument() {
public void sample() {
setValue(counters.nerror);
}
});
root.addCounter("ndeadlock", new Instrument() {
public void sample() {
setValue(counters.ndeadlock);
}
});
root.addCounter("ntimeout", new Instrument() {
public void sample() {
setValue(counters.ntimeout);
}
});
// Note: #that are waiting for their locks.
root.addCounter("nwaiting", new Instrument() {
public void sample() {
setValue(counters.nwaiting);
}
});
// Note: #that have acquired locks are executing concurrently.
root.addCounter("nrunning", new Instrument() {
public void sample() {
setValue(counters.nrunning);
}
});
// the maximum observed value for [nrunning].
root.addCounter("maxRunning", new Instrument() {
public void sample() {
setValue(counters.maxRunning);
}
});
// #of resource queues
root.addCounter("nresourceQueues", new Instrument() {
public void sample() {
setValue(resourceQueues.size());
}
});
root.addCounter("runState", new Instrument() {
public void sample() {
setValue(runState.toString());
}
});
/*
* Displays the #of tasks waiting on each resource queue for the
* lock (does not count the task at the head of the queue).
*
* @todo this could also be handled by dynamic reattachment of the
* counters.
*/
root.addCounter("queues", new Instrument() {
public void sample() {
final Iterator>>>> itr = resourceQueues
.entryIterator();
final LinkedList list = new LinkedList();
while (itr.hasNext()) {
final Map.Entry>>> entry = itr
.next();
final WeakReference>> queueRef = entry
.getValue();
final ResourceQueue> queue = queueRef
.get();
if (queue == null)
continue;
list.add(new ResourceQueueSize(queue));
}
final Object[] a = list.toArray();
Arrays.sort(a);
final StringBuilder sb = new StringBuilder();
for (Object t : a) {
sb.append(t.toString());
sb.append(" ");
}
setValue(sb.toString());
}
});
}
return root;
}
private CounterSet root;
/**
* Helper class pairs up the resource with its sampled queue size and allows
* ordering of the samples. E.g., by size or by resource.
*
* @author Bryan Thompson
* @version $Id$
*/
private class ResourceQueueSize implements Comparable {
final R resource;
int size;
public ResourceQueueSize(ResourceQueue> queue){
resource = queue.getResource();
size = queue.getQueueSize();
}
public int compareTo(ResourceQueueSize arg0) {
// resource name order.
// return resource.compareTo(arg0.resource);
// descending queue size order.
return arg0.size - size;
}
public String toString() {
return "(" + resource + "," + size + ")";
}
}
/**
* Counters for the {@link NonBlockingLockManager}.
*
* @author Bryan Thompson
* @version $Id$
*/
protected static class Counters {
/**
* The #of tasks that were accepted by the service (running total).
*/
public long naccepted;
/**
* The #of tasks that were rejected by the service (running total).
*/
public long nrejected;
/**
* The #of tasks that have been started on the delegate {@link Executor}
* (running total).
*/
public long nstarted;
/**
* The #of tasks that whose execution on the delegate {@link Executor}
* is complete (either by normal completion or by error, but only for
* tasks which were executed on the delegate) (running total).
*/
public long nended;
/**
* The #of tasks that were cancelled (running total).
*/
public long ncancel;
/**
* The #of tasks whose exception was set (running total).
*/
public long nerror;
/**
* The #of tasks that deadlocked when they attempted to acquire their
* locks (running total). Note that a task MAY retry lock acquisition
* and this counter will be incremented each time it does so and then
* deadlocks.
*/
public long ndeadlock;
/**
* The #of tasks that timed out when they attempted to acquire their
* locks (running total). Note that a task MAY retry lock acquisition
* and this counter will be incremented each time it does so and then
* times out.
*/
public long ntimeout;
/**
* #of tasks that are currently waiting on locks. This is the effective
* queue length of the {@link NonBlockingLockManager}. To get the
* actual queue length you need to add this to the length of the queue
* for the delegate {@link Executor}.
*
* FIXME This counter can be off since a task does not "know" when it is
* placed onto the [waitingTasks] queue and therefore can not decrement
* the counter conditionally if the task is cancelled or if an exception
* is set. Right now the counter is only decremented if the task begins
* to execute. This is a problem since this is the primary indication of
* the total size of the {@link NonBlockingLockManager} as a queue
* feeding its delegate {@link Executor}.
*/
public int nwaiting;
/**
* #of tasks that have acquired their locks and are concurrently
* executing. This is the true measure of concurrency.
*/
public int nrunning;
/**
* The maximum observed value of {@link #nrunning}.
*/
public int maxRunning;
}
/**
* Counters for various things.
*/
final Counters counters = new Counters();
/**
* Create a lock manager. No concurrency limit imposed when
* predeclareLocks is true
as deadlocks are
* impossible and we do not maintain a WAITS_FOR graph.
*
* @param maxConcurrency
* The maximum multi-programming level (ignored if
* predeclareLocks is true
).
* @param predeclareLocks
* When true
, operations MUST declare all locks
* before they begin to execute. This makes possible several
* efficiencies and by sorting the resources in each lock request
* into a common order we are able to avoid deadlocks entirely.
* @param delegate
* The service on which the tasks will be executed.
*
* Note: The delegate MUST NOT use a bounded queue or
* cause tasks to be run in the caller's thread. The use of a
* {@link SynchronousQueue} or an unbounded
* {@link LinkedBlockingQueue} for the delegate's
* workQueue are both acceptable.
*
* Note: If {@link Executor#execute(Runnable)} blocks for the
* delegate then the {@link AcceptTask} will also block
* and this class will be non-responsive until the delegate
* has accepted each [waitingTask] for execution. Some
* {@link Executor}s can cause the task to be run in the
* caller's thread, which would be the {@link AcceptTask} itself
* and which also has the effect of causing this class to be
* non-responsive until the task is complete.
*/
public NonBlockingLockManager(final int maxConcurrency,
final boolean predeclareLocks, final Executor delegate) {
this(maxConcurrency, predeclareLocks, true/* sortLockRequests */,
delegate);
}
/**
* Create a lock manager. No concurrency limit imposed when
* predeclareLocks is true
as deadlocks are
* impossible and we do not maintain a WAITS_FOR graph.
*
* @param maxConcurrency
* The maximum multi-programming level (ignored if
* predeclareLocks is true
).
* @param predeclareLocks
* When true
, operations MUST declare all locks
* before they begin to execute. This makes possible several
* efficiencies and by sorting the resources in each lock request
* into a common order we are able to avoid deadlocks entirely.
* @param sortLockRequests
* This option indicates whether or not the resources in a lock
* request will be sorted before attempting to acquire the locks
* for those resources. Normally true
this option
* MAY be disabled for testing purposes. It is an error to
* disable this option if predeclareLocks is
* false
.
* @param delegate
* The service on which the tasks will be executed.
*
* Note: The delegate MUST NOT use a bounded queue or
* cause tasks to be run in the caller's thread. The use of a
* {@link SynchronousQueue} or an unbounded
* {@link LinkedBlockingQueue} for the delegate's
* workQueue are both acceptable.
*
* Note: If {@link Executor#execute(Runnable)} blocks for the
* delegate then the {@link AcceptTask} will also block
* and this class will be non-responsive until the delegate
* has accepted each [waitingTask] for execution. Some
* {@link Executor}s can cause the task to be run in the
* caller's thread, which would be the {@link AcceptTask} itself
* and which also has the effect of causing this class to be
* non-responsive until the task is complete.
*/
NonBlockingLockManager(final int maxConcurrency,
final boolean predeclareLocks, final boolean sortLockRequests,
final Executor delegate) {
if (maxConcurrency < 2 && !predeclareLocks) {
throw new IllegalArgumentException(
"maxConcurrency: must be 2+ unless you are predeclaring locks, not "
+ maxConcurrency);
}
if (predeclareLocks && !sortLockRequests) {
/*
* This is required since we do not maintain TxDag when locks are
* predeclare and therefore can not detect deadlocks. Sorting with
* predeclared locks avoids the possibility of deadlocks so we do
* not need the TxDag (effectively, it means that all locks that can
* be requested by an operation are sorted since they are
* predeclared and acquired in one go).
*/
throw new IllegalArgumentException(
"Sorting of lock requests MUST be enabled when locks are being predeclared.");
}
if (delegate == null)
throw new IllegalArgumentException();
this.predeclareLocks = predeclareLocks;
this.sortLockRequests = sortLockRequests;
if (predeclareLocks) {
/*
* Note: waitsFor is NOT required if we will acquire all locks at
* once for a given operation since we can simply sort the lock
* requests for each operation into a common order, thereby making
* deadlock impossible!
*
* Note: waitsFor is also NOT required if we are using only a single
* threaded system.
*
* Note: if you allocate waitsFor here anyway then you can measure
* the cost of deadlock detection. As far as I can tell it is
* essentially zero when locks are predeclared.
*/
waitsFor = null;
// waitsFor = new TxDag(maxConcurrency);
} else {
/*
* Construct the directed graph used to detect deadlock cycles.
*/
waitsFor = new TxDag(maxConcurrency);
}
this.delegate = delegate;
// start service.
service.submit(new AcceptTask());
// change the run state.
lock.lock();
try {
setRunState(RunState.Running);
} finally {
lock.unlock();
}
}
/**
* {@link FutureTask} which executes once it holds its locks.
*
* @author Bryan Thompson
* @version $Id$
*
* @param
* The generic type of the outcome for the {@link Future}.
*/
protected class LockFutureTask extends FutureTaskMon {
private final R[] resource;
private final long lockTimeout;
private final int maxLockTries;
/**
* Incremented each time a deadlock is detected. We will not retry if
* {@link #maxLockTries} is exceeded.
*/
private int ntries = 0;
/**
* The timestamp in nanoseconds when this task was accepted. This is
* used to decide whether the {@link #lockTimeout} has expired.
*/
final private long acceptTime = System.nanoTime();
/**
* The set of {@link ResourceQueue}s for which this task owns a lock
* (is a member of the granted group) (NOT THREAD SAFE).
*
* Note: This collection is required in order for the
* {@link ResourceQueue}s for which the task has asserted a lock
* request to remain strongly reachable. Without such hard references
* the {@link ResourceQueue}s would be asynchronously cleared from the
* {@link NonBlockingLockManager#resourceQueues} collection by the
* garbage collector.
*/
private final LinkedHashSet>> lockedResources = new LinkedHashSet>>();
/**
* True if the {@link #lockTimeout} has expired when measured against
* now.
*/
protected boolean isTimeout() {
return (System.nanoTime() - acceptTime) >= lockTimeout;
}
private final Object task;
public String toString() {
return super.toString() + //
"{resources=" + Arrays.toString(resource) + //
", done=" + isDone() + //
", cancelled=" + isCancelled() + //
", ntries=" + ntries +
"}";
}
public LockFutureTask(final R[] resource, final Callable task,
final long timeout, final int maxLockTries) {
super(task);
this.resource = resource;
this.lockTimeout = timeout;
this.maxLockTries = maxLockTries;
this.task = task;
}
public LockFutureTask(final R[] resources, final Runnable task,
final T val, final long timeout, final int maxLockTries) {
super(task, val);
this.resource = resources;
this.lockTimeout = timeout;
this.maxLockTries = maxLockTries;
this.task = task;
}
/**
* The resource(s) that are pre-declared by the task. {@link #call()}
* will ensure that the task as a lock on these resources before it
* invokes {@link #run()} to execution the task.
*/
public R[] getResource() {
return resource;
}
/**
* The elapsed nanoseconds the task waited to acquire its locks.
*/
public long getLockLatency() {
return nanoTime_lockLatency;
}
private long nanoTime_lockLatency;
/**
* The maximum #of times that the task will attempt to acquire its locks
* (positive integer).
*/
public int getMaxLockTries() {
return maxLockTries;
}
/**
* The timeout (milliseconds) or ZERO (0L) for an infinite timeout.
*/
public long getLockTimeout() {
return lockTimeout;
}
/**
* Extended signal {@link NonBlockingLockManager#stateChanged} when the
* task completes, to track counters, and also exposed to the outer
* class.
*/
@Override
protected void setException(final Throwable t) {
super.setException(t);
lock.lock();
try {
if (DEBUG)
log.debug("Exception: " + this + ", cause=" + t, t);
counters.nerror++;
/*
* Note: Not known to be running, hence assume waiting (this
* method is sometimes called before the task begins to
* execute).
*/
final boolean waiting = true;
releaseLocks(this,waiting);
stateChanged.signal();
} finally {
lock.unlock();
}
}
/**
* Extended signal {@link NonBlockingLockManager#stateChanged} when the
* task completes and to track counters.
*/
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
final boolean ret = super.cancel(mayInterruptIfRunning);
lock.lock();
try {
if (DEBUG)
log.debug("Cancelled: " + this);
counters.ncancel++;
/*
* Note: Not known to be running, hence assume waiting (this
* method is sometimes called before the task begins to
* execute).
*/
final boolean waiting = true;
releaseLocks(this, waiting);
stateChanged.signal();
} finally {
lock.unlock();
}
return ret;
}
/**
* Extended signal {@link NonBlockingLockManager#stateChanged} when the
* task completes and to track counters.
*/
@Override
public void run() {
/*
* Increment by the amount of time that the task was waiting to
* acquire its lock(s).
*
* Note: This is being measured from the time when the task was
* accepted by submit() on the outer class and counts all time until
* the task begins to execute with its locks held.
*/
if (task instanceof AbstractTask
&& ((AbstractTask) task).getTaskCounters() instanceof WriteTaskCounters) {
final long lockWaitingTime = System.nanoTime() - acceptTime;
((WriteTaskCounters) ((AbstractTask) task).getTaskCounters()).lockWaitingNanoTime
.addAndGet(lockWaitingTime);
}
synchronized (counters) {
counters.nstarted++;
counters.nwaiting--;
counters.nrunning++;
if (counters.nrunning > counters.maxRunning) {
counters.maxRunning = counters.nrunning;
}
}
try {
if(DEBUG)
log.debug("Running: "+this);
super.run();
} finally {
lock.lock();
try {
if(DEBUG)
log.debug("Did run: "+this);
synchronized (counters) {
counters.nended++;
counters.nrunning--;
}
/*
* The task is KNOWN to not be waiting since it was running.
*/
final boolean waiting = false;
releaseLocks(this, waiting);
stateChanged.signal();
} finally {
lock.unlock();
}
}
}
}
/**
* Add if absent and return a {@link ResourceQueue} for the named resource.
*
* @param resource
* The resource.
*
* @return The {@link ResourceQueue}.
*/
private ResourceQueue> declareResource(final R resource) {
// test 1st to avoid creating a new ResourceQueue if it already exists.
ResourceQueue> resourceQueue = resourceQueues
.get(resource);
// not found, so create a new ResourceQueue for that resource.
resourceQueue = new ResourceQueue>(resource);
// put if absent.
final ResourceQueue> oldval = resourceQueues
.putIfAbsent(resource, resourceQueue);
if (oldval != null) {
// concurrent insert, so use the winner's resource queue.
return oldval;
}
// we were the winner, so return the our new resource queue.
return resourceQueue;
}
/**
* Submit a task for execution. The task will wait until it holds the
* declared locks. It will then execute. This method is non-blocking. The
* caller must use {@link FutureTask#get()} to await the outcome.
*
* @param resource
* An array of resources whose locks are required to execute the
* task.
* @param task
* The task to be executed.
*
* @throws IllegalArgumentException
* if resource is null
or if any element
* of that array is null
.
* @throws IllegalArgumentException
* if the task is null
.
*
* @throws RejectedExecutionException
* if the task can not be queued for execution (including if the
* service is not running or if a blocking queue was used and
* the queue is at capacity).
*
* @todo add variant for Runnable target.
*
* @todo get rid [lockTimeout] since you can do get(timeout) on the Future
* and the task will be cancelled unless it is complete by the
* timeout.
*
* @todo get rid of [maxLockTries] since you can specify a timeout and that
* will determine how much effort will be put into attempting to work
* around a deadlock?
*/
public Future submit(final R[] resource, final Callable task) {
return submit(resource, task, TimeUnit.SECONDS,
Long.MAX_VALUE/* timeout */, 1/* maxLockTries */);
}
// public Future submit(final LockCallable task) {
//
// return submit(task.getResource(), task);
//
// }
public Future submit(final R[] resource, final Callable task,
final TimeUnit unit, final long lockTimeout, final int maxLockTries) {
if (resource == null)
throw new IllegalArgumentException();
for (R r : resource) {
if (r == null)
throw new IllegalArgumentException();
}
if (task == null)
throw new IllegalArgumentException();
if (maxLockTries <= 0)
throw new IllegalArgumentException();
/*
* Note: We clone the resources to avoid side-effects on the caller if
* the resources are sorted (below) and also to prevent the caller from
* changing the declared locks after they submit the task.
*/
final R[] a = resource.clone();
if (sortLockRequests) {
/*
* Sort the resources in the lock request.
*
* Note: Sorting the resources reduces the chance of a deadlock and
* excludes it entirely when predeclaration of locks is also used.
*
* Note: This will throw an exception if the "resource" does not
* implement Comparable.
*/
Arrays.sort(a);
}
lock.lock();
try {
switch (runState) {
case Running: {
final LockFutureTask future = new LockFutureTask(a, task,
lockTimeout, maxLockTries);
try {
acceptedTasks.add(future);
counters.naccepted++;
} catch (IllegalStateException ex) {
counters.nrejected++;
throw new RejectedExecutionException(ex);
}
stateChanged.signal();
return future;
}
default:
counters.nrejected++;
throw new RejectedExecutionException("runState=" + runState);
}
} finally {
lock.unlock();
}
}
/**
* Used to run the {@link AcceptTask} and the {@link MonitorTask}.
*
* FIXME Monitor this service using a {@link ThreadPoolExecutorStatisticsTask} to convert
* {@link Counters#nrunning} and {@link Counters#nwaiting} into moving
* averages.
*/
private final ExecutorService service = Executors
.newSingleThreadExecutor(new DaemonThreadFactory(getClass()
.getName()));
/**
* The service run state.
*/
private volatile RunState runState = RunState.Starting;
/**
* Lock used to protect changes various state changes, including change to
* the {@link #runState}.
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* Condition is signaled whenever the {@link AcceptTask} needs to wake up.
*/
private final Condition stateChanged = lock.newCondition();
/**
* Tasks accepted but not yet waiting on their locks. Tasks are moved from
* here to the {@link #waitingTasks} asynchronously in order to de-couple
* the caller from {@link DeadlockException}s or periods when the system is
* at the maximum multi-programming level and can not accept another lock
* request.
*/
final private BlockingQueue> acceptedTasks = new LinkedBlockingQueue>();
/**
* Tasks whose lock requests are in the appropriate {@link ResourceQueue}s
* but which are not yet executing.
*/
private final BlockingQueue> waitingTasks = new LinkedBlockingQueue>();
/**
* {@link Runnable} drains the {@link #acceptedTasks} queue and manages
* state changes in the {@link ResourceQueue}s. Once a task is holding all
* necessary locks, the task is submitted to the delegate {@link Executor}
* for execution. This thread is also responsible for monitoring the
* {@link Future}s and releasing locks for a {@link Future} it is complete.
*
* @author Bryan Thompson
* @version $Id$
*/
private class AcceptTask implements Runnable {
public void run() {
while (true) {
switch (runState) {
case Starting: {
awaitStateChange(Starting);
continue;
}
case Running: {
while (processAcceptedTasks() || processWaitingTasks()) {
// do work
}
awaitStateChange(Running);
continue;
}
case Shutdown: {
while (processAcceptedTasks() || processWaitingTasks()) {
/*
* Do work.
*
* Note: will run anything already accepted. That is
* intentional. Once the lock manager is shutdown it will no
* longer accept tasks, but it will process those tasks
* which it has already accepted.
*/
}
lock.lock();
try {
if (acceptedTasks.isEmpty() && waitingTasks.isEmpty()) {
/*
* There is no more work to be performed so we can
* change the runState.
*/
if(INFO)
log.info("No more work.");
if (runState.val < RunState.ShutdownNow.val) {
setRunState(RunState.ShutdownNow);
break;
}
}
} finally {
lock.unlock();
}
awaitStateChange(Shutdown);
continue;
}
case ShutdownNow: {
/*
* Cancel all tasks, clearing all queues. Note that only
* tasks which are on [runningTasks] need to be interrupted
* as tasks on the other queues are NOT running.
*/
if(INFO)
log.info(runState);
cancelTasks(acceptedTasks.iterator(), false/* mayInterruptIfRunning */);
cancelTasks(waitingTasks.iterator(), false/* mayInterruptIfRunning */);
lock.lock();
try {
if (runState.val < RunState.Halted.val) {
setRunState(RunState.Halted);
}
} finally {
lock.unlock();
}
// fall through.
}
case Halted: {
if (INFO)
log.info(runState);
// Done.
return;
}
default:
throw new AssertionError();
} // switch(runState)
} // while(true)
} // run
/**
* IFF there is no work that could be performed and we are in the
* expected {@link RunState} then this blocks until someone signals
* {@link NonBlockingLockManager#stateChanged}. That signal can come
* either from submitting a new task, from a running task that
* completes, from a task being cancelled, or from a task whose
* exception was set.
*
* @see LockFutureTask
* @see NonBlockingLockManager#submit(Comparable[], Callable, TimeUnit, long, int)
*/
private void awaitStateChange(final RunState expected) {
lock.lock();
try {
/*
* While we hold the lock we verify that there really is no work
* to be done and that we are in the expected run state. Then
* and only then do we wait on [stateChanged].
*/
if (runState != expected) {
// In a different run state.
return;
}
if (!acceptedTasks.isEmpty() || !waitingTasks.isEmpty()) {
// Some work can be done.
return;
}
if (INFO)
log.info("Waiting...");
stateChanged.await();
if (INFO)
log.info("Woke up...");
} catch (InterruptedException ex) {
// someone woke us up.
} finally {
lock.unlock();
}
}
/**
* Cancel all tasks and remove them from the queue.
*
* @param tasks
* The tasks.
*/
private void cancelTasks(
final Iterator> itr,
final boolean mayInterruptIfRunning) {
while (itr.hasNext()) {
final LockFutureTask extends Object> t = itr.next();
t.cancel(mayInterruptIfRunning);
itr.remove();
}
}
/**
* Processes accepted tasks, adding lock requests for each in turn:
*
* - If requesting locks for a task would exceed the configured
* multi-programming level then we do not issue the request and return
* immediately. The task is left on the accepted queue and will be
* retried later.
* - If the lock requests for a task would cause a deadlock and the
* #of retries has been exceeded, then set the {@link DeadlockException}
* on the {@link Future} and drop the task. Otherwise ntries was
* incremented and we just ignore the task for now and will retry it
* again later.
* - If the timeout for the lock requests has already expired, then
* set set {@link TimeoutException} on the {@link Future} and drop the
* task.
*
*
* @return true iff any tasks were moved to the waiting queue.
*/
private boolean processAcceptedTasks() {
int nmoved = 0;
final Iterator> itr = acceptedTasks
.iterator();
while (itr.hasNext()) {
final LockFutureTask extends Object> t = itr.next();
if (t.isCancelled()) {
// already cancelled, e.g., by the caller.
itr.remove();
continue;
}
if (t.isTimeout()) {
// set exception on the task.
t.setException(new java.util.concurrent.TimeoutException());
counters.ntimeout++;
itr.remove();
continue;
}
int nvertices = -1;
lock.lock();
try {
// #of vertices before we request the locks.
if (waitsFor != null)
nvertices = waitsFor.size();
if (waitsFor != null && waitsFor.isFull()) {
/*
* Note: When TxDag is used we MUST NOT add the lock
* requests if it would exceed the configured
* multi-programming capacity (an exception would be
* thrown by TxDag). Therefore we stop processing
* accepted tasks and will wait until a running task
* completes so we can start another one.
*
* Note: when tasks != transactions we need to wait
* until a transaction completes. This could require
* multiple tasks to complete if they are tasks for the
* same transaction.
*/
if (INFO)
log.info("Maximum multi-programming capacity.");
return nmoved > 0;
}
/*
* Modify the state of the ResourceQueue(s) and the optional
* TxDag to reflect the lock requests. The method will
* release the lock requests if a deadlock would arise.
*
* Note: Can thrown DeadlockException.
*/
t.ntries++;
if (waitsFor != null) {
/*
* Declare the vertex.
*
* Note: The problem here is twofold.
*
* On the one hand vertices are not being released
* automatically when there are no more edges for a
* transaction. Even with removeEdges(tx,waiting) the
* vertex for the _other_ transaction is not being
* removed automatically.
*
* On the other hand, one vertex can be added per lock
* declared by the transaction and one more vertex for
* the transaction itself.
*
* The way to solve this is to declare the vertex for
* the transaction before we request the lock
* _regardless_ of whether an edge needs to be added and
* to remove it when the lock is released (which we
* already do). That way adding an edge will never cause
* a new vertex to be defined automagically.
*/
waitsFor.lookup(t, true/* insert */);
}
// log.warn("Requesting locks: "+t);
requestLocks(t);
} catch (Throwable t2) {
// /*
// * Note: Since this method does not support 2PL we know that the
// * task was not waiting on any transactions (and hence that none are
// * waiting on it). All of its lock requests will be at the end of
// * any resource queues.
// */
// final boolean waiting = true;
//
// releaseLocks(t, waiting);
// set exception on the task (clears locks)
t.setException(t2);
if (waitsFor != null) {
/*
* Paranoia check to make sure that we did not leave
* anything in the WAITS_FOR graph.
*/
final int nafter = waitsFor.size();
if (nvertices != nafter) {
throw new AssertionError("#vertices: before="
+ nvertices + ", after=" + nafter);
}
}
if ((t2 instanceof DeadlockException)) {
/*
* This is the ONLY expected exception when we issue the
* lock requests.
*/
log.warn("Deadlock: " + this + ", task=" + t /* , ex */);
counters.ndeadlock++;
if ((t.ntries < t.maxLockTries)) {
log.warn("Will retry task: " + t);
// leave on queue to permit retry.
continue;
} else {
log.error("Deadlock not resolved: " + this
+ ", task=" + t);
}
} else {
/*
* Anything else is an internal error.
*/
log.error("Internal error: task=" + t, t2);
}
/*
* Remove task from the accepted queue since we will not
* re-run it.
*
* Note: We DO retry tasks which result in a deadlock so
* they stay in the queue.
*/
itr.remove();
} finally {
lock.unlock();
}
/*
* FIXME if the task is holding all of its locks then start it
* immediately. Note also that we are grabbing and releasing the
* #lock quite in the AcceptTask's thread. And presumably in
* run() as well.
*/
// if(holdsAllLocks(t)) {
//
// }
waitingTasks.add(t);
counters.nwaiting++;
nmoved++;
itr.remove();
if (DEBUG) // moved to the waiting queue.
log.debug("Waiting: " + t);
}
if (INFO && nmoved > 0)
log.info("#moved=" + nmoved);
return nmoved > 0;
}
/**
* For each task waiting to run:
*
* - if the task has been cancelled, then remove it from the waiting
* tasks queue
* - if the lock timeout has expired, then set an exception on the
* task and remove it from the waiting task queue and remove its lock
* requests from the various {@link ResourceQueue}s
* - if the lock requests for that task have been granted then submit
* the task to execute on the delegate and move it to the running tasks
* queue
* - if the delegate rejects the task, then it is NOT removed from
* the waiting tasks queue and this method returns immediately
*
*
* @return true
if any tasks were moved to the running
* tasks list.
*/
private boolean processWaitingTasks() {
final Iterator> itr = waitingTasks
.iterator();
int nstarted = 0;
while (itr.hasNext()) {
final LockFutureTask extends Object> t = itr.next();
if (t.isCancelled()) {
// cancelled while awaiting locks.
itr.remove();
continue;
}
if (t.isTimeout()) {
// timeout while awaiting locks.
t.setException(new java.util.concurrent.TimeoutException());
itr.remove();
counters.ntimeout++;
continue;
}
final boolean holdsLocks;
lock.lock();
try {
holdsLocks = holdsAllLocks(t);
} finally {
lock.unlock();
}
if (holdsLocks) {
// holding locks, so execute the task.
if (INFO)
log.info("Executing: " + t);
try {
/*
* Note: FutureTask will take can of updating its state
* before/after the runnable target.
*
* Note: If delegate.execute() blocks then the acceptor
* thread will also block and this class will be
* non-responsive until the delegate had accepted each
* [waitingTask] for execution. Some executors can cause
* the task to be run in the caller's thread, which would
* be the AcceptTask itself.
*/
assert !lock.isHeldByCurrentThread();
delegate.execute(t);
} catch (RejectedExecutionException t2) {
/*
* We can't queue this task now so we stop processing
* the waiting tasks. We will pick up on those tasks
* again the next time this method is invoked.
*/
if(INFO)
log.info("Delegate is busy.");
return nstarted > 0;
}
itr.remove();
nstarted++;
continue;
}
}
if (INFO && nstarted > 0)
log.info("#started=" + nstarted);
return nstarted > 0;
}
} // AcceptTask
public boolean isOpen() {
return runState == RunState.Running;
}
public boolean isShutdown() {
switch (runState) {
case Shutdown:
case ShutdownNow:
case Halted:
return true;
}
return false;
}
public boolean isTerminated() {
return runState == RunState.Halted;
}
public void shutdown() {
lock.lock();
try {
if (runState.val < RunState.Shutdown.val) {
setRunState(RunState.Shutdown);
}
} finally {
lock.unlock();
}
}
public void shutdownNow() {
lock.lock();
try {
if (runState.val < RunState.ShutdownNow.val) {
setRunState(RunState.ShutdownNow);
}
} finally {
lock.unlock();
}
}
/**
* Change the {@link #runState}.
*
* @param newval
* The new value.
*
* @throws IllegalStateException
* if the state transition is illegal.
* @throws IllegalMonitorStateException
* if the current thread does not hold the {@link #lock}.
*/
private final void setRunState(final RunState newval) {
if (!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
if (!runState.isTransitionLegal(newval)) {
throw new IllegalStateException("runState=" + runState
+ ", but newValue=" + newval);
}
if (runState != newval) {
if (INFO)
log.info("Set runState=" + newval);
runState = newval;
stateChanged.signal();
}
}
/**
* Update the {@link ResourceQueue}s and the optional {@link TxDag} to
* reflect the lock requests for the task. The operation is atomic and
* succeeds iff the lock requests could be issued without deadlock. If an
* error occurs then the lock requests will not have been registered on the
* {@link TxDag}.
*
* @param task
* The task.
*
* @throws DeadlockException
* If the lock request(s) would cause a deadlock.
* @throws IllegalStateException
* If locks are being predeclared and there are already locks
* held by the operation.
*/
private void requestLocks(final LockFutureTask task)
throws DeadlockException {
if (task == null)
throw new IllegalArgumentException();
if(!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
switch(runState) {
case ShutdownNow:
case Halted:
/*
* No new lock requests are permitted once we reach ShutdownNow.
*/
throw new IllegalStateException("runState=" + runState);
}
if (task.resource.length == 0)
return; // NOP.
if (predeclareLocks) {
// verify that no locks are held for this operation.
if (!task.lockedResources.isEmpty()) {
/*
* The operation has already declared some locks. Since
* [predeclareLocks] is true it is not permitted to grow the set
* of declared locks, so we throw an exception.
*/
throw new IllegalStateException(
"Operation already has lock(s): " + task);
}
}
if (DEBUG)
log.debug("Acquiring lock(s): " + Arrays.toString(task.resource));
if (waitsFor != null) {
/*
* Detect deadlocks using TxDag.
*
* We take this in three stages.
*
* 1. Collect the set of distinct tasks which are already in the
* resource queues for the lock requests declared by this task.
*
* 2. If that set is NOT empty, then add edges to TxDag for each
* element of that set. If a DeadlockException is thrown then we can
* not issue those lock requests at this time.
*
* 3. Add the task to each of the resource queues.
*/
/*
* Collect the set of tasks on which this task must wait.
*/
final LinkedHashSet> predecessors = new LinkedHashSet>();
for (R r : (R[]) task.resource) {
// make sure queue exists for this resource.
final ResourceQueue> resourceQueue = declareResource(r);
if (!resourceQueue.queue.isEmpty()) {
predecessors.addAll(resourceQueue.queue);
}
}
if(!predecessors.isEmpty()) {
/*
* Add edges to the WAITS_FOR graph for each task on which this
* task must wait.
*
* Note: throws DeadlockException if the lock requests would
* cause a deadlock.
*
* Note: If an exception is thrown then the state of the TxDag
* is unchanged (the operation either succeeds or fails
* atomically).
*
* FIXME In fact, predeclaring locks appears sufficient to avoid
* deadlocks as long as we make issue the lock requests
* atomically for each task. This means that TxDag would only be
* useful for 2PL, and this class (NonBlockingLockManager)
* currently does not support 2PL (because it couples the
* concepts of the task and the transaction together).
*/
waitsFor.addEdges(task, predecessors.toArray());
}
/*
* Now that we have updated TxDag and know that the lock requests do
* not cause a deadlock, we register those requests on the
* ResourceQueues.
*/
for (R r : (R[]) task.resource) {
// make sure queue exists for this resource.
final ResourceQueue> resourceQueue = declareResource(r);
/*
* Add a lock request for this resource.
*/
resourceQueue.queue.add(task);
/*
* Add the resource queue to the set of queues whose locks are
* held by this task.
*/
task.lockedResources.add(resourceQueue);
}
} else {
/*
* When we are not using the WAITS_FOR graph all we have to do is
* add the task to each of the resource queues. It will run once it
* is at the head of each resource queue into which it is placed by
* its lock requests.
*/
for (R r : (R[]) task.resource) {
// make sure queue exists for this resource.
final ResourceQueue> resourceQueue = declareResource(r);
/*
* Add a lock request for this resource.
*/
resourceQueue.queue.add(task);
/*
* Add the resource queue to the set of queues whose locks are held
* by this task.
*/
task.lockedResources.add(resourceQueue);
}
}
}
/**
* Return true
iff the task holds all of its declared locks.
*
* @param task
* The task.
*
* @return true
iff it holds its locks.
*/
private boolean holdsAllLocks(final LockFutureTask extends Object> task) {
if (!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
for (R r : (R[]) task.resource) {
final ResourceQueue> resourceQueue = resourceQueues
.get(r);
assert resourceQueue != null : "resource=" + r;
if (!resourceQueue.isGranted(task)) {
return false;
}
}
return true;
}
/**
* Release all locks for the task.
*
* @param task
* The task.
* @param waiting
* When false, caller asserts that this transaction it is NOT
* waiting on any other transaction. This assertion is used to
* optimize the update of the path count matrix by simply
* removing the row and column associated with this transaction.
* When [waiting == true], a less efficient procedure is used to
* update the path count matrix.
*
* Do NOT specify [waiting == false] unless you know
* that the transaction is NOT waiting. In general, this
* knowledge is available to the 2PL locking package.
*/
private void releaseLocks(final LockFutureTask t,
final boolean waiting) {
if (!lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException();
if (DEBUG)
log.debug("Releasing locks: " + t);
try {
final Iterator>> itr = t.lockedResources
.iterator();
while (itr.hasNext()) {
final ResourceQueue> resourceQueue = itr
.next();
/*
* Remove lock request from resource queue
*/
if (!resourceQueue.queue.remove(t)) {
log.error("Lock request not found: resource="
+ resourceQueue.getResource() + ", task=" + t);
}
// remove lock from collection since no longer held by task.
itr.remove();
}
} finally {
if (waitsFor != null) {
/*
* At this point there are edges in the WAITS_FOR graph and the
* task is no longer on any of the resource queues.
*/
synchronized (waitsFor) {
try {
waitsFor.removeEdges(t, waiting);
/*
* Release the vertex (if any) in the WAITS_FOR graph.
*
* Note: Since we always declare a vertex before we
* request the locks for a task this method SHOULD NOT
* return [false].
*/
if (waitsFor.releaseVertex(t)) {
log.error("No vertex? " + t);
}
} catch (Throwable t2) {
log.error(this, t2);
}
}
}
}
}
public String toString() {
// return getCounters().toString();
return getClass().getName() + //
"{ accepted=" + acceptedTasks.size() + //
", waiting=" + waitingTasks.size() + //
// ", running=" + runningTasks.size() + //
", #started="+counters.nstarted+//
", #ended="+counters.nended+//
", #cancel="+counters.ncancel+//
", #timeout="+counters.ntimeout+//
", #error="+counters.nerror+//
", #deadlock="+counters.ndeadlock+//
(waitsFor!=null?", vertices="+waitsFor.size():"")+//
"}";
}
/**
* Unbounded queue of operations waiting to gain an exclusive lock on a
* resource. By default, the queue imposes a "fair" schedule for access to
* the resource. Deadlocks among resources are detected using a
* WAITS_FOR
graph that is shared by all resources and
* transactions for a given database instance.
*
* Note: deadlock detection MAY be disabled when all lock requests are (a)
* pre-declared; and (b) sorted. When disabled the WAITS_FOR
* graph is NOT maintained.
*
* @author Bryan Thompson
* @version $Id$
*
* @param
* The generic type of the elements in the queue.
*
* @see LockManager
* @see TxDag
*/
protected class ResourceQueue> {
/**
* The resource whose access is controlled by this object.
*/
final private R resource;
/**
* The queue of transactions seeking access to the {@link #resource}.
* The object at the head of the queue is the transaction with the lock
* on the resource.
*/
final BlockingQueue queue = new LinkedBlockingQueue(/* unbounded */);
/**
* The resource whose locks are administeded by this object.
*/
public R getResource() {
return resource;
}
/**
* True iff there is a granted group.
*/
public boolean isLocked() {
return !queue.isEmpty();
}
/**
* The #of pending requests for a lock on the resource.
*/
public int getQueueSize() {
return Math.max(0, queue.size() - 1);
}
/**
* Return true if the transaction currently holds the lock.
*
* @param tx
* The transaction.
*/
public boolean isGranted(final T tx) {
if (tx == null) {
throw new IllegalArgumentException();
}
return queue.peek() == tx;
}
/**
* Note: This uses {@link LinkedBlockingQueue#toString()} to serialize
* the state of the resource queue so the result will be consistent per
* the contract of that method and
* {@link LinkedBlockingQueue#iterator()}.
*/
public String toString() {
return getClass().getSimpleName() + "{resource=" + resource
+ ", queue=" + queue.toString() + "}";
}
/**
* Create a queue of lock requests for a resource.
*
* @param resource
* The resource.
*/
public ResourceQueue(final R resource) {
if (resource == null)
throw new IllegalArgumentException();
this.resource = resource;
}
// /**
// * Return iff the tx currently holds the lock on the resource.
// *
// * @throws IllegalStateException
// * if the tx is not in the granted group.
// */
// private final void assertOwnsLock(final T tx) {
//
// if (queue.peek() != tx) {
//
// throw new IllegalStateException("Does not hold lock: " + tx);
//
// }
//
// }
// /**
// * Request a lock on the resource. If the queue is empty or if the task
// * already owns the lock then return immediately. Otherwise, update the
// * optional {@link TxDag} to determine if the a deadlock would result.
// * If no deadlock would result, then add the task to the queue.
// *
// * @param tx
// * The transaction.
// *
// * @return true
if the lock is granted for that
// * transaction (either it already owns the lock or the resource
// * queue is empty so it is immediately granted the lock).
// *
// * @throws DeadlockException
// * if the request would cause a deadlock among the running
// * transactions.
// */
// public void add(final T tx) throws DeadlockException {
//
// if (tx == null)
// throw new IllegalArgumentException();
//
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
//
// if (DEBUG)
// log.debug("enter: tx=" + tx + ", queue=" + this);
//
// // already locked.
// if (queue.peek() == tx) {
//
// /*
// * Note: This is being disallowed since we are not supporting
// * 2PL here and it should not be possible for the tx to already
// * own the lock. If you want to support 2PL then you would allow
// * this case.
// */
// throw new IllegalStateException("Already owns lock: tx=" + tx
// + ", queue=" + this);
//
// }
//
// if (queue.isEmpty()) {
//
// // the queue is empty so immediately grant the lock.
// queue.add(tx);
//
// if (INFO)
// log.info("Granted lock with empty queue: tx=" + tx
// + ", queue=" + this);
//
// // lock can be granted immediately.
// return;
//
// }
//
// /*
// * Update the WAITS_FOR graph since we are now going to wait on the
// * tx that currently holds this lock.
// *
// * We need to add an edge from this transaction to the transaction
// * that currently holds the lock for this resource. This indicates
// * that [tx] WAITS_FOR the operation that holds the lock.
// *
// * We need to do this for each predecessor in the queue so that the
// * correct WAITS_FOR edges remain when a predecessor is granted the
// * lock.
// *
// * FIXME The problem here is when there are two transactions share
// * two or more locks requests. If one transaction is already running
// * then the other transaction will wind up asserting one WAITS_FOR
// * edge for each of the shared lock requests. TxDag does not allow
// * more than a single WAITS_FOR edge count for the same source and
// * target and throws an IllegalStateException.
// *
// * This was not a problem in the old LockManager because we
// * incrementally grew the lock requests as they were granted. It is
// * a problem now because we issue all requests at once.
// *
// * One way to solve this is to add to each of the queues, receiving
// * back a Set of the predecessors. Merge those sets. That gives the
// * set of transactions on which the current task must wait. Then
// * create those edges. If any edge would cause a deadlock rollback
// * the changes to each of the resource queues (the TxDag request is
// * atomic so it will not have been modified).
// */
// if (waitsFor != null) {
//
// final Object[] predecessors = queue.toArray();
//
// /*
// * Note: this operation is atomic. If it fails, then none of
// * the edges were added.
// *
// * Note: throws DeadlockException.
// */
// try {
// waitsFor.addEdges(tx/* src */, predecessors);
// } catch(IllegalStateException ex) {
// System.err.println("task: "+tx);
// System.err.println("predecessors: "+Arrays.toString(predecessors));
// System.err.println("queue:"+toString());
// System.err.println(waitsFor.toString());
// waitsFor.addEdges(tx/* src */, predecessors);
// System.exit(1);
// }
//
// }
//
// /*
// * Now that we know that the request does not directly cause a
// * deadlock we add the request to the queue. The task will not
// * execute until it maeks it to the head of the queue.
// */
// queue.add(tx);
//
// }
//
// /**
// * Remove the tx from the {@link ResourceQueue}.
// *
// * @param tx
// * The transaction.
// *
// * @deprecated This should be optimized out.
// */
// public void remove(final T tx) {
//
// if (tx == null)
// throw new IllegalArgumentException();
//
// if(!lock.isHeldByCurrentThread())
// throw new IllegalMonitorStateException();
//
// if (queue.peek() != tx) {
//
// /*
// * Removing some tx which does not own the lock.
// */
//
// if (!queue.remove(tx)) {
//
// throw new AssertionError("Not in queue? tx=" + tx
// + ", queue=" + Arrays.toString(queue.toArray()));
//
// }
//
// return;
//
// }
//
// /*
// * Removing the tx that owns the lock.
// */
// if (tx != queue.remove()) {
//
// throw new AssertionError("Removed wrong tx?");
//
// }
//
// /*
// * We just removed the granted lock. Now we have to update the
// * WAITS_FOR graph to remove all edges whose source is a pending
// * transaction (for this resource) since those transactions are
// * waiting on the transaction that just released the lock.
// */
//
// if (DEBUG)
// log.debug("removed lock owner from queue: " + tx);
//
// if (waitsFor != null) {
//
// final Iterator itr = queue.iterator();
//
// synchronized (waitsFor) {
//
// while (itr.hasNext()) {
//
// final T pendingTx = itr.next();
//
// if (DEBUG)
// log.debug("Removing edge: pendingTx=" + pendingTx);
//
// waitsFor.removeEdge(pendingTx, tx);
//
// }
//
// }
//
// }
//
// }
} // ResourceQueue
}