diff options
Diffstat (limited to 'src/java/jogamp/common/util/locks/RecursiveLockImpl01CompleteFair.java')
-rw-r--r-- | src/java/jogamp/common/util/locks/RecursiveLockImpl01CompleteFair.java | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/java/jogamp/common/util/locks/RecursiveLockImpl01CompleteFair.java b/src/java/jogamp/common/util/locks/RecursiveLockImpl01CompleteFair.java new file mode 100644 index 0000000..bbd0a94 --- /dev/null +++ b/src/java/jogamp/common/util/locks/RecursiveLockImpl01CompleteFair.java @@ -0,0 +1,312 @@ +/** + * 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.locks.RecursiveLock; + +/** + * Reentrance locking toolkit, impl a complete fair FIFO scheduler + * + * <p> + * Sync object extends {@link AbstractOwnableSynchronizer}, hence monitoring is possible.</p> + */ +public class RecursiveLockImpl01CompleteFair implements RecursiveLock { + + private static class WaitingThread { + WaitingThread(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(Thread t) { + setExclusiveOwnerThread(t); + } + private final void setLockedStack(Throwable s) { + List<Throwable> ls = LockDebugUtil.getRecursiveLockTrace(); + if(s==null) { + ls.remove(lockedStack); + } else { + ls.add(s); + } + lockedStack = s; + } + // lock count by same thread + private int holdCount = 0; + // stack trace of the lock, only used if DEBUG + private Throwable lockedStack = null; + // waiting thread queue + final ArrayList<WaitingThread> queue = new ArrayList<WaitingThread>(); + } + private 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 <code>null</code>. + * @see com.jogamp.common.util.locks.Lock#DEBUG + */ + public final Throwable getLockedStack() { + synchronized(sync) { + return sync.lockedStack; + } + } + + public final Thread getOwner() { + synchronized(sync) { + return sync.getOwner(); + } + } + + public final boolean isOwner() { + synchronized(sync) { + return isOwner(Thread.currentThread()); + } + } + + public final boolean isOwner(Thread thread) { + synchronized(sync) { + return sync.getOwner() == thread ; + } + } + + public final boolean isLocked() { + synchronized(sync) { + return null != sync.getOwner(); + } + } + + public final boolean isLockedByOtherThread() { + synchronized(sync) { + return null != sync.getOwner() && Thread.currentThread() != sync.getOwner() ; + } + } + + public final int getHoldCount() { + synchronized(sync) { + return sync.holdCount; + } + } + + public final void validateLocked() { + 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()); + } + } + } + + 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 (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + } + + 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 && 0<sync.queue.size() ) ) { + + if ( 0 >= timeout ) { + // locked by other thread and no waiting requested + return false; + } + + // enqueue at the start + 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 (InterruptedException e) { + if( !wCur.signaledByUnlock ) { + sync.queue.remove(wCur); // O(n) + throw 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 ) ; + + if( 0 >= timeout ) { + // timed out + if(!wCur.signaledByUnlock) { + sync.queue.remove(wCur); // O(n) + } + if(cur == sync.getOwner()) { + sync.setOwner(null); + } + if(TRACE_LOCK || DEBUG) { + 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; + } + } + + + public final void unlock() { + synchronized(sync) { + unlock(null); + } + } + + public final void unlock(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(); + } + } + } + + public final int getQueueLength() { + synchronized(sync) { + return sync.queue.size(); + } + } + + 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(Thread t) { return null!=t ? "<"+t.getName()+">" : "<NULL>" ; } +} + |