1 /**
2  * Copyright 2010 JogAmp Community. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are
5  * permitted provided that the following conditions are met:
6  *
7  *    1. Redistributions of source code must retain the above copyright notice, this list of
8  *       conditions and the following disclaimer.
9  *
10  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
11  *       of conditions and the following disclaimer in the documentation and/or other materials
12  *       provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  * The views and conclusions contained in the software and documentation are those of the
25  * authors and should not be interpreted as representing official policies, either expressed
26  * or implied, of JogAmp Community.
27  */
28 
29 package jogamp.common.util.locks;
30 
31 import java.util.List;
32 import java.util.concurrent.locks.AbstractOwnableSynchronizer;
33 
34 import com.jogamp.common.util.locks.RecursiveLock;
35 
36 /**
37  * Reentrance locking toolkit, impl a non-complete fair FIFO scheduler.
38  * <p>
39  * Fair scheduling is not guaranteed due to the usage of {@link Object#notify()},
40  * however new lock-applicants will wait if queue is not empty for {@link #lock()}
41  * and {@link #tryLock(long) tryLock}(timeout>0).</p>
42  *
43  * <p>
44  * Sync object extends {@link AbstractOwnableSynchronizer}, hence monitoring is possible.</p>
45  */
46 public class RecursiveLockImpl01Unfairish implements RecursiveLock {
47 
48     /* package */ static interface Sync {
getOwner()49         Thread getOwner();
isOwner(Thread t)50         boolean isOwner(Thread t);
setOwner(Thread t)51         void setOwner(Thread t);
52 
getLockedStack()53         Throwable getLockedStack();
setLockedStack(Throwable s)54         void setLockedStack(Throwable s);
55 
getHoldCount()56         int getHoldCount();
incrHoldCount(Thread t)57         void incrHoldCount(Thread t);
decrHoldCount(Thread t)58         void decrHoldCount(Thread t);
59 
getQSz()60         int getQSz();
incrQSz()61         void incrQSz();
decrQSz()62         void decrQSz();
63     }
64 
65     @SuppressWarnings("serial")
66     /* package */ static class SingleThreadSync extends AbstractOwnableSynchronizer implements Sync {
SingleThreadSync()67         /* package */ SingleThreadSync() {
68             super();
69         }
70         @Override
getOwner()71         public final Thread getOwner() {
72             return getExclusiveOwnerThread();
73         }
74         @Override
isOwner(final Thread t)75         public boolean isOwner(final Thread t) {
76             return getExclusiveOwnerThread()==t;
77         }
78         @Override
setOwner(final Thread t)79         public final void setOwner(final Thread t) {
80             setExclusiveOwnerThread(t);
81         }
82         @Override
getLockedStack()83         public final Throwable getLockedStack() {
84             return lockedStack;
85         }
86         @Override
setLockedStack(final Throwable s)87         public final void setLockedStack(final Throwable s) {
88             final List<Throwable> ls = LockDebugUtil.getRecursiveLockTrace();
89             if(s==null) {
90                 ls.remove(lockedStack);
91             } else {
92                 ls.add(s);
93             }
94             lockedStack = s;
95         }
96         @Override
getHoldCount()97         public final int getHoldCount() { return holdCount; }
98         @Override
incrHoldCount(final Thread t)99         public void incrHoldCount(final Thread t) { holdCount++; }
100         @Override
decrHoldCount(final Thread t)101         public void decrHoldCount(final Thread t) { holdCount--; }
102 
103         @Override
getQSz()104         public final int getQSz() { return qsz; }
105         @Override
incrQSz()106         public final void incrQSz() { qsz++; }
107         @Override
decrQSz()108         public final void decrQSz() { qsz--; }
109 
110         /** lock count by same thread */
111         private int holdCount = 0;
112         /** queue size of waiting threads */
113         private int qsz = 0;
114         /** stack trace of the lock, only used if DEBUG */
115         private Throwable lockedStack = null;
116     }
117 
118     protected final Sync sync;
119 
RecursiveLockImpl01Unfairish(final Sync sync)120     public RecursiveLockImpl01Unfairish(final Sync sync) {
121         this.sync = sync;
122     }
123 
RecursiveLockImpl01Unfairish()124     public RecursiveLockImpl01Unfairish() {
125         this(new SingleThreadSync());
126     }
127 
128     /**
129      * Returns the Throwable instance generated when this lock was taken the 1st time
130      * and if {@link com.jogamp.common.util.locks.Lock#DEBUG} is turned on, otherwise it returns always <code>null</code>.
131      * @see com.jogamp.common.util.locks.Lock#DEBUG
132      */
getLockedStack()133     public final Throwable getLockedStack() {
134         synchronized(sync) {
135             return sync.getLockedStack();
136         }
137     }
138 
139     @Override
getOwner()140     public final Thread getOwner() {
141         synchronized(sync) {
142             return sync.getOwner();
143         }
144     }
145 
146     @Override
isOwner(final Thread thread)147     public final boolean isOwner(final Thread thread) {
148         synchronized(sync) {
149             return sync.isOwner(thread);
150         }
151     }
152 
153     @Override
isLocked()154     public final boolean isLocked() {
155         synchronized(sync) {
156             return null != sync.getOwner();
157         }
158     }
159 
160     @Override
isLockedByOtherThread()161     public final boolean isLockedByOtherThread() {
162         synchronized(sync) {
163             final Thread o = sync.getOwner();
164             return null != o && Thread.currentThread() != o ;
165         }
166     }
167 
168     @Override
getHoldCount()169     public final int getHoldCount() {
170         synchronized(sync) {
171             return sync.getHoldCount();
172         }
173     }
174 
175     @Override
validateLocked()176     public final void validateLocked() throws RuntimeException {
177         synchronized(sync) {
178             if ( !sync.isOwner(Thread.currentThread()) ) {
179                 if ( null == sync.getOwner() ) {
180                     throw new RuntimeException(threadName(Thread.currentThread())+": Not locked: "+toString());
181                 }
182                 if(null!=sync.getLockedStack()) {
183                     sync.getLockedStack().printStackTrace();
184                 }
185                 throw new RuntimeException(Thread.currentThread()+": Not owner: "+toString());
186             }
187         }
188     }
189 
190     @Override
lock()191     public final void lock() {
192         synchronized(sync) {
193             try {
194                 if(!tryLock(TIMEOUT)) {
195                     if(null!=sync.getLockedStack()) {
196                         sync.getLockedStack().printStackTrace();
197                     }
198                     throw new RuntimeException("Waited "+TIMEOUT+"ms for: "+toString()+" - "+threadName(Thread.currentThread()));
199                 }
200             } catch (final InterruptedException e) {
201                 throw new RuntimeException("Interrupted", e);
202             }
203         }
204     }
205 
206     @Override
tryLock(long timeout)207     public final boolean tryLock(long timeout) throws InterruptedException {
208         synchronized(sync) {
209             final Thread cur = Thread.currentThread();
210             if(TRACE_LOCK) {
211                 System.err.println("+++ LOCK 0 "+toString()+", timeout "+timeout+" ms, cur "+threadName(cur));
212             }
213             if (sync.isOwner(cur)) {
214                 sync.incrHoldCount(cur);
215                 if(TRACE_LOCK) {
216                     System.err.println("+++ LOCK XR "+toString()+", cur "+threadName(cur));
217                 }
218                 return true;
219             }
220 
221             if ( sync.getOwner() != null || ( 0<timeout && 0<sync.getQSz() ) ) {
222 
223                 if ( 0 >= timeout ) {
224                     // locked by other thread and no waiting requested
225                     if(TRACE_LOCK) {
226                         System.err.println("+++ LOCK XY "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
227                     }
228                     return false;
229                 }
230 
231                 sync.incrQSz();
232                 do {
233                     final long t0 = System.currentTimeMillis();
234                     sync.wait(timeout);
235                     timeout -= System.currentTimeMillis() - t0;
236                 } while (null != sync.getOwner() && 0 < timeout) ;
237                 sync.decrQSz();
238 
239                 if( 0 >= timeout && sync.getOwner() != null ) {
240                     // timed out
241                     if(TRACE_LOCK) {
242                         System.err.println("+++ LOCK XX "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
243                     }
244                     return false;
245                 }
246 
247                 if(TRACE_LOCK) {
248                     System.err.println("+++ LOCK X1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms");
249                 }
250             } else if(TRACE_LOCK) {
251                 System.err.println("+++ LOCK X0 "+toString()+", cur "+threadName(cur));
252             }
253 
254             sync.setOwner(cur);
255             sync.incrHoldCount(cur);
256 
257             if(DEBUG) {
258                 sync.setLockedStack(new Throwable("Previously locked by "+toString()));
259             }
260             return true;
261         }
262     }
263 
264 
265     @Override
unlock()266     public final void unlock() {
267         synchronized(sync) {
268             unlock(null);
269         }
270     }
271 
272     @Override
unlock(final Runnable taskAfterUnlockBeforeNotify)273     public void unlock(final Runnable taskAfterUnlockBeforeNotify) {
274         synchronized(sync) {
275             validateLocked();
276             final Thread cur = Thread.currentThread();
277 
278             sync.decrHoldCount(cur);
279 
280             if (sync.getHoldCount() > 0) {
281                 if(TRACE_LOCK) {
282                     System.err.println("--- LOCK XR "+toString()+", cur "+threadName(cur));
283                 }
284                 return;
285             }
286 
287             sync.setOwner(null);
288             if(DEBUG) {
289                 sync.setLockedStack(null);
290             }
291             if(null!=taskAfterUnlockBeforeNotify) {
292                 taskAfterUnlockBeforeNotify.run();
293             }
294 
295             if(TRACE_LOCK) {
296                 System.err.println("--- LOCK X0 "+toString()+", cur "+threadName(cur)+", signal any");
297             }
298             sync.notify();
299         }
300     }
301 
302     @Override
getQueueLength()303     public final int getQueueLength() {
304         synchronized(sync) {
305             return sync.getQSz();
306         }
307     }
308 
309     @Override
toString()310     public String toString() {
311         return syncName()+"[count "+sync.getHoldCount()+
312                            ", qsz "+sync.getQSz()+", owner "+threadName(sync.getOwner())+"]";
313     }
314 
syncName()315     /* package */ final String syncName() {
316         return "<"+Integer.toHexString(this.hashCode())+", "+Integer.toHexString(sync.hashCode())+">";
317     }
threadName(final Thread t)318     /* package */ final String threadName(final Thread t) { return null!=t ? "<"+t.getName()+">" : "<NULL>" ; }
319 }
320 
321