jogamp.common.util.locks.RecursiveLockImpl01CompleteFair Maven / Gradle / Ivy
/**
* Copyright 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.common.util.locks;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.AbstractOwnableSynchronizer;
import com.jogamp.common.util.SourcedInterruptedException;
import com.jogamp.common.util.locks.RecursiveLock;
/**
* Reentrance locking toolkit, impl a complete fair FIFO scheduler
*
*
* Sync object extends {@link AbstractOwnableSynchronizer}, hence monitoring is possible.
*/
public class RecursiveLockImpl01CompleteFair implements RecursiveLock {
private static class WaitingThread {
WaitingThread(final Thread t) {
thread = t;
signaledByUnlock = false;
}
final Thread thread;
boolean signaledByUnlock; // if true, it's also removed from queue
}
@SuppressWarnings("serial")
private static class Sync extends AbstractOwnableSynchronizer {
private Sync() {
super();
}
private final Thread getOwner() {
return getExclusiveOwnerThread();
}
private final void setOwner(final Thread t) {
setExclusiveOwnerThread(t);
}
private final void setLockedStack(final Throwable s) {
final List ls = LockDebugUtil.getRecursiveLockTrace();
if(s==null) {
ls.remove(lockedStack);
} else {
ls.add(s);
}
lockedStack = s;
}
/** lock count by same thread */
private int holdCount = 0;
/** waiting thread queue */
final ArrayList queue = new ArrayList();
/** stack trace of the lock, only used if DEBUG */
private Throwable lockedStack = null;
}
private final Sync sync = new Sync();
public RecursiveLockImpl01CompleteFair() {
}
/**
* Returns the Throwable instance generated when this lock was taken the 1st time
* and if {@link com.jogamp.common.util.locks.Lock#DEBUG} is turned on, otherwise it returns always null
.
* @see com.jogamp.common.util.locks.Lock#DEBUG
*/
public final Throwable getLockedStack() {
synchronized(sync) {
return sync.lockedStack;
}
}
@Override
public final Thread getOwner() {
synchronized(sync) {
return sync.getOwner();
}
}
@Override
public final boolean isOwner(final Thread thread) {
synchronized(sync) {
return sync.getOwner() == thread ;
}
}
@Override
public final boolean isLocked() {
synchronized(sync) {
return null != sync.getOwner();
}
}
@Override
public final boolean isLockedByOtherThread() {
synchronized(sync) {
final Thread o = sync.getOwner();
return null != o && Thread.currentThread() != o ;
}
}
@Override
public final int getHoldCount() {
synchronized(sync) {
return sync.holdCount;
}
}
@Override
public final void validateLocked() throws RuntimeException {
synchronized(sync) {
if ( Thread.currentThread() != sync.getOwner() ) {
if ( null == sync.getOwner() ) {
throw new RuntimeException(threadName(Thread.currentThread())+": Not locked: "+toString());
}
if(null!=sync.lockedStack) {
sync.lockedStack.printStackTrace();
}
throw new RuntimeException(Thread.currentThread()+": Not owner: "+toString());
}
}
}
@Override
public final void lock() {
synchronized(sync) {
try {
if(!tryLock(TIMEOUT)) {
if(null!=sync.lockedStack) {
sync.lockedStack.printStackTrace();
}
throw new RuntimeException("Waited "+TIMEOUT+"ms for: "+toString()+" - "+threadName(Thread.currentThread()));
}
} catch (final InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
}
}
@Override
public final boolean tryLock(long timeout) throws InterruptedException {
synchronized(sync) {
final Thread cur = Thread.currentThread();
if(TRACE_LOCK) {
System.err.println("+++ LOCK 0 "+toString()+", cur "+threadName(cur));
}
if (sync.getOwner() == cur) {
++sync.holdCount;
if(TRACE_LOCK) {
System.err.println("+++ LOCK XR "+toString()+", cur "+threadName(cur));
}
return true;
}
if ( sync.getOwner() != null || ( 0= timeout ) {
// locked by other thread and no waiting requested
if(TRACE_LOCK) {
System.err.println("+++ LOCK XY "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
}
return false;
}
// enqueue at the start
final WaitingThread wCur = new WaitingThread(cur);
sync.queue.add(0, wCur);
do {
final long t0 = System.currentTimeMillis();
try {
sync.wait(timeout);
timeout -= System.currentTimeMillis() - t0;
} catch (final InterruptedException e) {
if( !wCur.signaledByUnlock ) {
sync.queue.remove(wCur); // O(n)
throw SourcedInterruptedException.wrap(e); // propagate interruption not send by unlock
} else if( cur != sync.getOwner() ) {
// Issued by unlock, but still locked by other thread
//
timeout -= System.currentTimeMillis() - t0;
if(TRACE_LOCK) {
System.err.println("+++ LOCK 1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms, signaled: "+wCur.signaledByUnlock);
}
if(0 < timeout) {
// not timed out, re-enque - lock was 'stolen'
wCur.signaledByUnlock = false;
sync.queue.add(sync.queue.size(), wCur);
}
} // else: Issued by unlock, owning lock .. expected!
}
} while ( cur != sync.getOwner() && 0 < timeout ) ;
Thread.interrupted(); // clear slipped interrupt
if( 0 >= timeout && cur != sync.getOwner() ) {
// timed out
if(!wCur.signaledByUnlock) {
sync.queue.remove(wCur); // O(n)
}
if(TRACE_LOCK) {
System.err.println("+++ LOCK XX "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
}
return false;
}
++sync.holdCount;
if(TRACE_LOCK) {
System.err.println("+++ LOCK X1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
}
} else {
++sync.holdCount;
if(TRACE_LOCK) {
System.err.println("+++ LOCK X0 "+toString()+", cur "+threadName(cur));
}
}
sync.setOwner(cur);
if(DEBUG) {
sync.setLockedStack(new Throwable("Previously locked by "+toString()));
}
return true;
}
}
@Override
public final void unlock() {
synchronized(sync) {
unlock(null);
}
}
@Override
public final void unlock(final Runnable taskAfterUnlockBeforeNotify) {
synchronized(sync) {
validateLocked();
final Thread cur = Thread.currentThread();
--sync.holdCount;
if (sync.holdCount > 0) {
if(TRACE_LOCK) {
System.err.println("--- LOCK XR "+toString()+", cur "+threadName(cur));
}
return;
}
if(DEBUG) {
sync.setLockedStack(null);
}
if(null!=taskAfterUnlockBeforeNotify) {
taskAfterUnlockBeforeNotify.run();
}
if(sync.queue.size() > 0) {
// fair, wakeup the oldest one ..
// final WaitingThread oldest = queue.removeLast();
final WaitingThread oldest = sync.queue.remove(sync.queue.size()-1);
sync.setOwner(oldest.thread);
if(TRACE_LOCK) {
System.err.println("--- LOCK X1 "+toString()+", cur "+threadName(cur)+", signal: "+threadName(oldest.thread));
}
oldest.signaledByUnlock = true;
oldest.thread.interrupt(); // Propagate SecurityException if it happens
} else {
sync.setOwner(null);
if(TRACE_LOCK) {
System.err.println("--- LOCK X0 "+toString()+", cur "+threadName(cur)+", signal any");
}
sync.notify();
}
}
}
@Override
public final int getQueueLength() {
synchronized(sync) {
return sync.queue.size();
}
}
@Override
public String toString() {
return syncName()+"[count "+sync.holdCount+
", qsz "+sync.queue.size()+", owner "+threadName(sync.getOwner())+"]";
}
private final String syncName() {
return "<"+Integer.toHexString(this.hashCode())+", "+Integer.toHexString(sync.hashCode())+">";
}
private final String threadName(final Thread t) { return null!=t ? "<"+t.getName()+">" : "" ; }
}