/*
* eXist Open Source Native XML Database
* Copyright (C) 2005-2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* File: ReentrantLock.java
*
* Originally written by Doug Lea and released into the public domain.
* This may be used for any purposes whatsoever without acknowledgment.
* Thanks for the assistance and support of Sun Microsystems Labs,
* and everyone contributing, testing, and using this code.
*
* $Id$
*
*/
package org.exist.storage.lock;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.exist.util.LockException;
/**
* A lock with the same semantics as builtin
* Java synchronized locks: Once a thread has a lock, it
* can re-obtain it any number of times without blocking.
* The lock is made available to other threads when
* as many releases as acquires have occurred.
*
* The lock has a timeout: a read lock will be released if the
* timeout is reached.
*/
public class ReentrantReadWriteLock implements Lock {
private static final int WAIT_CHECK_PERIOD = 200;
private class SuspendedWaiter {
Thread thread;
int lockMode;
int lockCount;
public SuspendedWaiter(Thread thread, int lockMode, int lockCount) {
this.thread = thread;
this.lockMode = lockMode;
this.lockCount = lockCount;
}
}
private final static Logger LOG = Logger.getLogger(ReentrantReadWriteLock.class);
protected Object id_ = null;
protected Thread owner_ = null;
protected Stack suspendedThreads = new Stack();
protected int holds_ = 0;
public int mode_ = Lock.NO_LOCK;
// private long timeOut_ = 240000L;
private Stack modeStack = new Stack();
private int writeLocks = 0;
private boolean DEBUG = false;
private Stack seStack;
private LockListener listener = null;
private ReentrantLock lock = DeadlockDetection.getLock();
private Condition monitor = lock.newCondition();
public ReentrantReadWriteLock(Object id) {
id_ = id;
if (DEBUG)
seStack = new Stack();
}
public String getId() {
return id_.toString();
}
/* @deprecated Use other method
* @see org.exist.storage.lock.Lock#acquire()
*/
public boolean acquire() throws LockException {
return acquire(Lock.READ_LOCK);
}
public boolean acquire(int mode) throws LockException {
if (mode == Lock.NO_LOCK) {
LOG.warn("acquired with no lock !");
return true;
}
if (Thread.interrupted())
throw new LockException();
Thread caller = Thread.currentThread();
lock.lock();
try {
WaitingThread waitingOnResource;
if (caller == owner_) {
++holds_;
modeStack.push(new Integer(mode));
if (mode == Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
return true;
} else if (owner_ == null) {
owner_ = caller;
holds_ = 1;
modeStack.push(new Integer(mode));
if (mode== Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
return true;
} else if ((waitingOnResource = DeadlockDetection.deadlockCheckResource(caller, owner_)) != null) {
// LOG.warn("DEADLOCK detected on " + getId() + ": " + owner_.getName() + " -> " + caller.getName());
waitingOnResource.suspendWaiting();
SuspendedWaiter suspended = new SuspendedWaiter(owner_, mode_, holds_);
suspendedThreads.push(suspended);
owner_ = caller;
holds_ = 1;
modeStack.push(new Integer(mode));
if (mode== Lock.WRITE_LOCK)
writeLocks++;
mode_ = mode;
listener = waitingOnResource;
return true;
} else {
long start = System.currentTimeMillis();
DeadlockDetection.addCollectionWaiter(caller, this);
// LOG.warn(caller.getName() + " waiting on lock held by " + owner_.getName());
try {
for (;;) {
monitor.await(WAIT_CHECK_PERIOD, TimeUnit.MILLISECONDS);
if ((waitingOnResource = DeadlockDetection.deadlockCheckResource(caller, owner_)) != null) {
// LOG.warn("DEADLOCK detected after wakeUp: " + owner_.getName() + " -> " + caller.getName());
waitingOnResource.suspendWaiting();
SuspendedWaiter suspended = new SuspendedWaiter(owner_, mode_, holds_);
suspendedThreads.push(suspended);
owner_ = caller;
holds_ = 1;
modeStack.push(new Integer(mode));
if (mode== Lock.WRITE_LOCK)
writeLocks++;
mode_ = mode;
listener = waitingOnResource;
DeadlockDetection.clearCollectionWaiter(owner_);
return true;
} else if (caller == owner_) {
++holds_;
modeStack.push(new Integer(mode));
if (mode == Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
DeadlockDetection.clearCollectionWaiter(owner_);
return true;
} else if (owner_ == null) {
owner_ = caller;
holds_ = 1;
modeStack.push(new Integer(mode));
if (mode == Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
DeadlockDetection.clearCollectionWaiter(owner_);
return true;
} // else {
// long waitTime = timeOut_ - (System.currentTimeMillis() - start);
// if (waitTime <= 0) {
// // blocking thread found: if the lock is read only, remove it
// if (writeLocks == 0) {
// our System.out.println("releasing blocking thread " + owner_.getName() + " on " + id_ + " (" + modeStack.size() + " acquisitions)");
// if (DEBUG) {
// LOG.debug("Lock was acquired by :");
// while (!seStack.isEmpty()) {
// StackTraceElement[] se = (StackTraceElement[])seStack.pop();
// LOG.debug(se);
// se = null;
// }
// }
// owner_ = caller;
// while (!modeStack.isEmpty()) {
// Integer top = (Integer)modeStack.pop();
// top = null;
// }
// holds_ = 1;
// modeStack.push(new Integer(mode));
// if (DEBUG) {
// Throwable t = new Throwable();
// seStack.push(t.getStackTrace());
// }
// mode_ = mode;
// DeadlockDetection.clearCollectionWaiter(owner_);
// return true;
// } else
// LOG.warn("Write lock timed out");
// if (DEBUG) {
// LOG.debug("Lock was acquired by :");
// while (!seStack.isEmpty()) {
// StackTraceElement[] se = (StackTraceElement[])seStack.pop();
// LOG.debug(se);
// se = null;
// }
// }
// DeadlockDetection.clearCollectionWaiter(owner_);
// throw new LockException("time out while acquiring a lock");
// }
// }
}
} catch (InterruptedException ex) {
monitor.signal();
throw new LockException("interrupted while waiting for lock");
}
}
} finally {
lock.unlock();
}
}
public void wakeUp() {
lock.lock();
try {
monitor.signal();
} finally {
lock.unlock();
}
}
public boolean attempt(int mode) {
Thread caller = Thread.currentThread();
lock.lock();
try {
if (caller == owner_) {
++holds_;
modeStack.push(new Integer(mode));
if (mode == Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
return true;
} else if (owner_ == null) {
owner_ = caller;
holds_ = 1;
modeStack.push(new Integer(mode));
if (mode == Lock.WRITE_LOCK)
writeLocks++;
if (DEBUG) {
Throwable t = new Throwable();
seStack.push(t.getStackTrace());
}
mode_ = mode;
return true;
} else {
return false;
}
} finally {
lock.unlock();
}
}
/* (non-Javadoc)
* @see org.exist.util.Lock#isLockedForWrite()
*/
public boolean isLockedForWrite() {
lock.lock();
try {
return writeLocks > 0;
} finally {
lock.unlock();
}
}
public boolean isLockedForRead(Thread owner) {
// always returns false for this lock
return false;
}
public boolean hasLock() {
lock.lock();
try {
return holds_ > 0;
} finally {
lock.unlock();
}
}
public boolean hasLock(Thread owner) {
return this.owner_ == owner;
}
public Thread getOwner() {
return this.owner_;
}
/* (non-Javadoc)
* @see org.exist.util.Lock#release(int)
*/
public void release(int mode) {
lock.lock();
try {
if (Thread.currentThread() != owner_) {
LOG.warn("Possible lock problem: thread "
+ Thread.currentThread()
+ " released a lock on "
+ getId()
+ " it didn't hold. Either the "
+ "thread was interrupted or it never acquired the lock. The lock was owned by: "
+ owner_);
if (DEBUG) {
LOG.debug("Lock was acquired by :");
while (!seStack.isEmpty()) {
StackTraceElement[] se = (StackTraceElement[]) seStack
.pop();
LOG.debug(se);
se = null;
}
}
return;
}
Integer top = (Integer) modeStack.pop();
mode_ = top.intValue();
top = null;
if (mode_ != mode) {
LOG.warn("Released lock of different type. Expected " + mode_
+ " got " + mode, new Throwable());
}
if (mode_ == Lock.WRITE_LOCK) {
// if (isCollectionLock)
// LOG.warn(owner_.getName() + " RELEASED WRITE lock", new Throwable());
writeLocks--;
}
if (DEBUG) {
StackTraceElement[] se = (StackTraceElement[]) seStack.pop();
se = null;
}
// LOG.debug("Lock " + getId() + " released by " + owner_.getName());
if (--holds_ == 0) {
if (!suspendedThreads.isEmpty()) {
SuspendedWaiter suspended = (SuspendedWaiter) suspendedThreads
.pop();
owner_ = suspended.thread;
mode_ = suspended.lockMode;
holds_ = suspended.lockCount;
} else {
owner_ = null;
mode_ = Lock.NO_LOCK;
monitor.signal();
}
}
if (listener != null) {
listener.lockReleased();
listener = null;
}
} finally {
lock.unlock();
}
}
public void release(int mode, int count) {
throw new UnsupportedOperationException(getClass().getName() +
" does not support releasing multiple locks");
}
/**
* Return the number of unreleased acquires performed
* by the current thread.
* Returns zero if current thread does not hold lock.
**/
public long holds() {
lock.lock();
try {
if (Thread.currentThread() != owner_)
return 0;
return holds_;
} finally {
lock.unlock();
}
}
public LockInfo getLockInfo() {
lock.lock();
try {
String lockType = mode_ == Lock.WRITE_LOCK ? LockInfo.WRITE_LOCK
: LockInfo.READ_LOCK;
return new LockInfo(LockInfo.COLLECTION_LOCK, lockType, getId(),
new String[] { owner_.getName() });
} finally {
lock.unlock();
}
}
}