1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.txn;
9 
10 import static com.sleepycat.je.txn.LockStatDefinition.GROUP_DESC;
11 import static com.sleepycat.je.txn.LockStatDefinition.GROUP_NAME;
12 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_OWNERS;
13 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_READ_LOCKS;
14 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_REQUESTS;
15 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_TOTAL;
16 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WAITERS;
17 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WAITS;
18 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WRITE_LOCKS;
19 
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.ConcurrentModificationException;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 
31 import com.sleepycat.je.DatabaseException;
32 import com.sleepycat.je.DeadlockException;
33 import com.sleepycat.je.EnvironmentMutableConfig;
34 import com.sleepycat.je.LockConflictException;
35 import com.sleepycat.je.LockNotAvailableException;
36 import com.sleepycat.je.LockNotGrantedException;
37 import com.sleepycat.je.LockStats;
38 import com.sleepycat.je.LockTimeoutException;
39 import com.sleepycat.je.StatsConfig;
40 import com.sleepycat.je.ThreadInterruptedException;
41 import com.sleepycat.je.TransactionTimeoutException;
42 import com.sleepycat.je.config.EnvironmentParams;
43 import com.sleepycat.je.dbi.DatabaseImpl;
44 import com.sleepycat.je.dbi.DbConfigManager;
45 import com.sleepycat.je.dbi.EnvConfigObserver;
46 import com.sleepycat.je.dbi.EnvironmentImpl;
47 import com.sleepycat.je.dbi.MemoryBudget;
48 import com.sleepycat.je.dbi.RangeRestartException;
49 import com.sleepycat.je.latch.Latch;
50 import com.sleepycat.je.latch.LatchFactory;
51 import com.sleepycat.je.latch.LatchSupport;
52 import com.sleepycat.je.utilint.DbLsn;
53 import com.sleepycat.je.utilint.IntStat;
54 import com.sleepycat.je.utilint.LongStat;
55 import com.sleepycat.je.utilint.StatGroup;
56 import com.sleepycat.je.utilint.TestHook;
57 import com.sleepycat.je.utilint.TinyHashSet;
58 
59 /**
60  * LockManager manages locks.
61  *
62  * Note that locks are counted as taking up part of the JE cache;
63  */
64 public abstract class LockManager implements EnvConfigObserver {
65 
66     /*
67      * The total memory cost for a lock is the Lock object, plus its entry and
68      * key in the lock hash table.
69      *
70      * The addition and removal of Lock objects, and the corresponding cost of
71      * their hashmap entry and key are tracked through the LockManager.
72      */
73     static final long TOTAL_LOCKIMPL_OVERHEAD =
74         MemoryBudget.LOCKIMPL_OVERHEAD +
75         MemoryBudget.HASHMAP_ENTRY_OVERHEAD +
76         MemoryBudget.LONG_OVERHEAD;
77 
78     static final long TOTAL_THINLOCKIMPL_OVERHEAD =
79         MemoryBudget.THINLOCKIMPL_OVERHEAD +
80         MemoryBudget.HASHMAP_ENTRY_OVERHEAD +
81         MemoryBudget.LONG_OVERHEAD;
82 
83     private static final long REMOVE_TOTAL_LOCKIMPL_OVERHEAD =
84         0 - TOTAL_LOCKIMPL_OVERHEAD;
85 
86     private static final long REMOVE_TOTAL_THINLOCKIMPL_OVERHEAD =
87         0 - TOTAL_THINLOCKIMPL_OVERHEAD;
88 
89     private static final long THINLOCK_MUTATE_OVERHEAD =
90         MemoryBudget.LOCKIMPL_OVERHEAD -
91         MemoryBudget.THINLOCKIMPL_OVERHEAD +
92         MemoryBudget.LOCKINFO_OVERHEAD;
93 
94     private static final List<ThreadLocker> EMPTY_THREAD_LOCKERS =
95         Collections.emptyList();
96 
97     /* Hook called after a lock is requested.. */
98     public static TestHook<Void> afterLockHook;
99 
100     int nLockTables = 1;
101     Latch[] lockTableLatches;
102     private final Map<Long,Lock>[] lockTables;          // keyed by LSN
103     private final boolean oldLockExceptions;
104     private final EnvironmentImpl envImpl;
105     private final MemoryBudget memoryBudget;
106 
107     private final StatGroup stats;
108     private final LongStat nRequests; /* number of time a request was made. */
109     private final LongStat nWaits;    /* number of time a request blocked. */
110 
111     private static RangeRestartException rangeRestartException =
112         new RangeRestartException();
113     private static boolean lockTableDump = false;
114 
115     /**
116      * Maps a thread to a set of ThreadLockers.  Currently this map is only
117      * maintained (non-null) in a replicated environment because it is only
118      * needed for determining when to throw LockPreemptedException.
119      *
120      * Access to the map need not be synchronized because it is a
121      * ConcurrentHashMap.  Access to the TinyHashSet stored for each thread
122      * need not be synchronized, since it is only accessed by a single thread.
123      *
124      * A TinyHashSet is used because typically only a single ThreadLocker per
125      * thread will be open at one time.
126      *
127      * @see ThreadLocker#checkPreempted
128      * [#16513]
129      */
130     private final Map<Thread, TinyHashSet<ThreadLocker>> threadLockers;
131 
132     /*
133      * @SuppressWarnings is used to stifle a type safety complaint about the
134      * assignment of lockTables = new Map[nLockTables]. There's no way to
135      * specify the type of the array.
136      */
137     @SuppressWarnings("unchecked")
LockManager(EnvironmentImpl envImpl)138     public LockManager(EnvironmentImpl envImpl) {
139 
140         DbConfigManager configMgr = envImpl.getConfigManager();
141         nLockTables = configMgr.getInt(EnvironmentParams.N_LOCK_TABLES);
142         oldLockExceptions =
143             configMgr.getBoolean(EnvironmentParams.LOCK_OLD_LOCK_EXCEPTIONS);
144         lockTables = new Map[nLockTables];
145         lockTableLatches = new Latch[nLockTables];
146         for (int i = 0; i < nLockTables; i++) {
147             lockTables[i] = new HashMap<Long,Lock>();
148             lockTableLatches[i] = LatchFactory.createExclusiveLatch(
149                 envImpl, "Lock Table " + i, true /*collectStats*/);
150         }
151         this.envImpl = envImpl;
152         memoryBudget = envImpl.getMemoryBudget();
153 
154         stats = new StatGroup(GROUP_NAME, GROUP_DESC);
155         nRequests = new LongStat(stats, LOCK_REQUESTS);
156         nWaits = new LongStat(stats, LOCK_WAITS);
157 
158         /* Initialize mutable properties and register for notifications. */
159         envConfigUpdate(configMgr, null);
160         envImpl.addConfigObserver(this);
161 
162         if (envImpl.isReplicated()) {
163             threadLockers =
164                 new ConcurrentHashMap<Thread, TinyHashSet<ThreadLocker>>();
165         } else {
166             threadLockers = null;
167         }
168     }
169 
170     /**
171      * Process notifications of mutable property changes.
172      */
envConfigUpdate(DbConfigManager configMgr, EnvironmentMutableConfig ignore)173     public void envConfigUpdate(DbConfigManager configMgr,
174                                 EnvironmentMutableConfig ignore) {
175         LockInfo.setDeadlockStackTrace(configMgr.getBoolean
176             (EnvironmentParams.TXN_DEADLOCK_STACK_TRACE));
177         setLockTableDump(configMgr.getBoolean
178             (EnvironmentParams.TXN_DUMPLOCKS));
179     }
180 
181     /**
182      * Called when the je.txn.dumpLocks property is changed.
183      */
setLockTableDump(boolean enable)184     static void setLockTableDump(boolean enable) {
185         lockTableDump = enable;
186     }
187 
getLockTableIndex(Long lsn)188     int getLockTableIndex(Long lsn) {
189         return (((int) lsn.longValue()) & 0x7fffffff) %
190             nLockTables;
191     }
192 
getLockTableIndex(long lsn)193     int getLockTableIndex(long lsn) {
194         return (((int) lsn) & 0x7fffffff) % nLockTables;
195     }
196 
197     /**
198      * Attempt to acquire a lock of <i>type</i> on <i>lsn</i>.  If the lock
199      * acquisition would result in a deadlock, throw an exception.<br> If the
200      * requested lock is not currently available, block until it is or until
201      * timeout milliseconds have elapsed.<br> If a lock of <i>type</i> is
202      * already held, return EXISTING.<br> If a WRITE lock is held and a READ
203      * lock is requested, return PROMOTION.<br>
204      *
205      * If a lock request is for a lock that is not currently held, return
206      * either NEW or DENIED depending on whether the lock is granted or
207      * not.<br>
208      *
209      * @param lsn The LSN to lock.
210      *
211      * @param locker The Locker to lock this on behalf of.
212      *
213      * @param type The lock type requested.
214      *
215      * @param timeout milliseconds to time out after if lock couldn't be
216      * obtained.  0 means block indefinitely.  Not used if nonBlockingRequest
217      * is true.
218      *
219      * @param nonBlockingRequest if true, means don't block if lock can't be
220      * acquired, and ignore the timeout parameter.
221      *
222      * @param jumpAheadOfWaiters grant the lock before other waiters, if any.
223      *
224      * @return a LockGrantType indicating whether the request was fulfilled or
225      * not.  LockGrantType.NEW means the lock grant was fulfilled and the
226      * caller did not previously hold the lock.  PROMOTION means the lock was
227      * granted and it was a promotion from READ to WRITE.  EXISTING means the
228      * lock was already granted (not a promotion).  DENIED means the lock was
229      * not granted because the timeout passed without acquiring the lock or
230      * timeout was 0 and the lock was not immediately available.
231      *
232      * @throws LockConflictException if lock could not be acquired.
233      *
234      * @throws IllegalArgumentException via db/cursor read/write methods, if
235      * non-transactional access to a replicated environment is attempted, and
236      * read-uncommitted is not specified.
237      */
lock(long lsn, Locker locker, LockType type, long timeout, boolean nonBlockingRequest, boolean jumpAheadOfWaiters, DatabaseImpl database)238     public LockGrantType lock(long lsn,
239                               Locker locker,
240                               LockType type,
241                               long timeout,
242                               boolean nonBlockingRequest,
243                               boolean jumpAheadOfWaiters,
244                               DatabaseImpl database)
245         throws LockConflictException, DatabaseException {
246 
247         assert timeout >= 0;
248 
249         /* No lock needed for dirty-read, return as soon as possible. */
250         if (type == LockType.NONE) {
251             return LockGrantType.NONE_NEEDED;
252         }
253 
254         /*
255          * Lock on locker before latching the lockTable to avoid having another
256          * notifier perform the notify before the waiter is actually waiting.
257          */
258         synchronized (locker) {
259             final LockGrantType grant = lockInternal(
260                 lsn, locker, type, timeout, nonBlockingRequest,
261                 jumpAheadOfWaiters, database);
262 
263             if (afterLockHook != null) {
264                 afterLockHook.doHook();
265             }
266 
267             return grant;
268         }
269     }
270 
lockInternal(long lsn, Locker locker, LockType type, long timeout, boolean nonBlockingRequest, boolean jumpAheadOfWaiters, DatabaseImpl database)271     private LockGrantType lockInternal(long lsn,
272                                        Locker locker,
273                                        LockType type,
274                                        long timeout,
275                                        boolean nonBlockingRequest,
276                                        boolean jumpAheadOfWaiters,
277                                        DatabaseImpl database)
278         throws DeadlockException, DatabaseException {
279 
280         Long nid = Long.valueOf(lsn);
281 
282         LockAttemptResult result = attemptLock(
283             nid, locker, type, nonBlockingRequest, jumpAheadOfWaiters);
284 
285         /* If we got the lock or a non-blocking lock was denied, return. */
286         if (result.success ||
287             result.lockGrant == LockGrantType.DENIED) {
288             assert nonBlockingRequest || result.success;
289             return result.lockGrant;
290         }
291 
292         if (LatchSupport.TRACK_LATCHES) {
293             if (!nonBlockingRequest) {
294                 LatchSupport.expectBtreeLatchesHeld(0);
295             }
296         }
297 
298         /*
299          * We must have gotten WAIT_* from the lock request. We know that
300          * this is a blocking request, because if it wasn't, Lock.lock
301          * would have returned DENIED. Go wait!
302          */
303         assert !nonBlockingRequest;
304         try {
305             boolean doWait = true;
306             boolean isImportunate = locker.getImportunate();
307 
308             /*
309              * Before blocking, check locker/txn timeout. We need to check here
310              * or lock timeouts will always take precedence and we'll never
311              * actually get any txn timeouts.
312              */
313             if (locker.isTimedOut()) {
314                 if (validateOwnership(nid, locker, type,
315                                       !isImportunate,
316                                       memoryBudget)) {
317                     doWait = false;
318                 } else if (isImportunate) {
319                     result = stealLock(nid, locker, type);
320                     if (result.success) {
321                         doWait = false;
322                     } else {
323                         /* Lock holder is non-preemptable, wait below. */
324                     }
325                 } else {
326                     /* throw a LockConflictException */
327                     throw makeTimeoutException(
328                         false /*isLockNotTxnTimeout*/, locker, lsn, type,
329                         result.lockGrant, result.useLock,
330                         locker.getTxnTimeout(), locker.getTxnStartMillis(),
331                         System.currentTimeMillis(), database);
332                 }
333             }
334 
335             boolean keepTime = (timeout > 0);
336             long startTime = (keepTime ? System.currentTimeMillis() : 0);
337             while (doWait) {
338                 locker.setWaitingFor(result.useLock);
339 
340                 try {
341                     locker.wait(timeout);
342                 } catch (InterruptedException IE) {
343                     throw new ThreadInterruptedException(envImpl, IE);
344                 }
345 
346                 boolean lockerTimedOut = locker.isTimedOut();
347                 long now = System.currentTimeMillis();
348                 boolean thisLockTimedOut =
349                     (keepTime && (now - startTime >= timeout));
350                 boolean isRestart =
351                     (result.lockGrant == LockGrantType.WAIT_RESTART);
352 
353                 /*
354                  * Re-check for ownership of the lock following wait.  If
355                  * we timed out and we don't have ownership then flush this
356                  * lock from both the waiters and owners while under the
357                  * lock table latch.  See SR 10103.
358                  */
359                 if (validateOwnership(nid, locker, type,
360                                       (lockerTimedOut ||
361                                       thisLockTimedOut ||
362                                       isRestart) &&
363                                       !isImportunate,
364                                       memoryBudget)) {
365                     break;
366                 } else if (isImportunate) {
367                     result = stealLock(nid, locker, type);
368                     if (result.success) {
369                         break;
370                     } else {
371                         /* Lock holder is non-preemptable, wait again. */
372                     }
373                 } else {
374                     /* After a restart conflict the lock will not be held. */
375                     if (isRestart) {
376                         throw rangeRestartException;
377                     }
378 
379                     if (thisLockTimedOut) {
380                         /* throw a LockConflictException */
381                         throw makeTimeoutException(
382                             true /*isLockNotTxnTimeout*/, locker, lsn, type,
383                             result.lockGrant, result.useLock,
384                             timeout, startTime, now, database);
385                     }
386 
387                     if (lockerTimedOut) {
388                         /* throw a LockConflictException */
389                         throw makeTimeoutException(
390                             false /*isLockNotTxnTimeout*/, locker, lsn, type,
391                             result.lockGrant, result.useLock,
392                             locker.getTxnTimeout(), locker.getTxnStartMillis(),
393                             now, database);
394                     }
395                 }
396             }
397         } finally {
398             locker.setWaitingFor(null);
399             assert EnvironmentImpl.maybeForceYield();
400         }
401 
402         /*
403          * After waiting for the lock, we must break out of the wait loop and
404          * add the lock to the locker.  This is true even for importunate
405          * lockers, since an existing lock (acquired via a release) will not be
406          * added to the locker by attemptLock. [#16879]
407          */
408         locker.addLock(nid, type, result.lockGrant);
409 
410         return result.lockGrant;
411     }
412 
413     /**
414      * Returns the Lockers that own a lock on the given LSN.  Note that when
415      * this method returns, there is nothing to prevent these lockers from
416      * releasing the lock or being closed.
417      */
getOwners(Long lsn)418     public abstract Set<LockInfo> getOwners(Long lsn);
419 
getOwnersInternal(Long lsn, int lockTableIndex)420     Set<LockInfo> getOwnersInternal(Long lsn, int lockTableIndex) {
421         /* Get the target lock. */
422         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
423         Lock useLock = lockTable.get(lsn);
424         if (useLock == null) {
425             return null;
426         }
427         return useLock.getOwnersClone();
428     }
429 
430     /**
431      * Returns the LockType if the given locker owns a lock on the given node,
432      * or null if the lock is not owned.
433      */
getOwnedLockType(Long lsn, Locker locker)434     public abstract LockType getOwnedLockType(Long lsn, Locker locker);
435 
getOwnedLockTypeInternal(Long lsn, Locker locker, int lockTableIndex)436     LockType getOwnedLockTypeInternal(Long lsn,
437                                       Locker locker,
438                                       int lockTableIndex) {
439         /* Get the target lock. */
440         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
441         Lock useLock = lockTable.get(lsn);
442         if (useLock == null) {
443             return null;
444         }
445         return useLock.getOwnedLockType(locker);
446     }
447 
isLockUncontended(Long lsn)448     public abstract boolean isLockUncontended(Long lsn);
449 
isLockUncontendedInternal(Long lsn, int lockTableIndex)450     boolean isLockUncontendedInternal(Long lsn, int lockTableIndex) {
451         /* Get the target lock. */
452         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
453         Lock useLock = lockTable.get(lsn);
454         if (useLock == null) {
455             return true;
456         }
457         return useLock.nWaiters() == 0 &&
458                useLock.nOwners() == 0;
459     }
460 
lookupLock(Long lsn)461     abstract Lock lookupLock(Long lsn)
462         throws DatabaseException;
463 
lookupLockInternal(Long lsn, int lockTableIndex)464     Lock lookupLockInternal(Long lsn, int lockTableIndex) {
465         /* Get the target lock. */
466         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
467         Lock useLock = lockTable.get(lsn);
468         return useLock;
469     }
470 
attemptLock(Long lsn, Locker locker, LockType type, boolean nonBlockingRequest, boolean jumpAheadOfWaiters)471     abstract LockAttemptResult attemptLock(Long lsn,
472                                            Locker locker,
473                                            LockType type,
474                                            boolean nonBlockingRequest,
475                                            boolean jumpAheadOfWaiters)
476         throws DatabaseException;
477 
attemptLockInternal(Long lsn, Locker locker, LockType type, boolean nonBlockingRequest, boolean jumpAheadOfWaiters, int lockTableIndex)478     LockAttemptResult attemptLockInternal(Long lsn,
479                                           Locker locker,
480                                           LockType type,
481                                           boolean nonBlockingRequest,
482                                           boolean jumpAheadOfWaiters,
483                                           int lockTableIndex)
484         throws DatabaseException {
485 
486         nRequests.increment();
487 
488         /* Get the target lock. */
489         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
490         Lock useLock = lockTable.get(lsn);
491         if (useLock == null) {
492             useLock = new ThinLockImpl();
493             lockTable.put(lsn, useLock);
494             memoryBudget.updateLockMemoryUsage
495                 (TOTAL_THINLOCKIMPL_OVERHEAD, lockTableIndex);
496         }
497 
498         /*
499          * Attempt to lock.  Possible return values are NEW, PROMOTION, DENIED,
500          * EXISTING, WAIT_NEW, WAIT_PROMOTION, WAIT_RESTART.
501          */
502         LockAttemptResult lar = useLock.lock
503             (type, locker, nonBlockingRequest, jumpAheadOfWaiters,
504              memoryBudget, lockTableIndex);
505         if (lar.useLock != useLock) {
506             /* The lock mutated from ThinLockImpl to LockImpl. */
507             useLock = lar.useLock;
508             lockTable.put(lsn, useLock);
509             /* We still have the overhead of the hashtable (locktable). */
510             memoryBudget.updateLockMemoryUsage
511                 (THINLOCK_MUTATE_OVERHEAD, lockTableIndex);
512         }
513         LockGrantType lockGrant = lar.lockGrant;
514         boolean success = false;
515 
516         /* Was the attempt successful? */
517         if ((lockGrant == LockGrantType.NEW) ||
518             (lockGrant == LockGrantType.PROMOTION)) {
519             locker.addLock(lsn, type, lockGrant);
520             success = true;
521         } else if (lockGrant == LockGrantType.EXISTING) {
522             success = true;
523         } else if (lockGrant == LockGrantType.DENIED) {
524             /* Locker.lock will throw LockNotAvailableException. */
525         } else {
526             nWaits.increment();
527         }
528         return new LockAttemptResult(useLock, lockGrant, success);
529     }
530 
makeTimeoutException( boolean isLockNotTxnTimeout, Locker locker, long lsn, LockType type, LockGrantType grantType, Lock useLock, long timeout, long start, long now, DatabaseImpl database)531     private LockConflictException makeTimeoutException(
532         boolean isLockNotTxnTimeout,
533         Locker locker,
534         long lsn,
535         LockType type,
536         LockGrantType grantType,
537         Lock useLock,
538         long timeout,
539         long start,
540         long now,
541         DatabaseImpl database) {
542 
543         /*
544          * getTimeoutInfo synchronizes on the lock table. The timeout exception
545          * must be created outside that synchronization block because its ctor
546          * invalidates the txn, sometimes synchronizing on the buddy locker.
547          * The order of mutex acquisition must always be 1) locker, 2) lock
548          * table.
549          */
550         TimeoutInfo info = getTimeoutInfo(
551             isLockNotTxnTimeout, locker, lsn, type, grantType, useLock,
552             timeout, start, now, database);
553 
554         LockConflictException ex =
555             isLockNotTxnTimeout ?
556             newLockTimeoutException(locker, info.message) :
557             newTxnTimeoutException(locker, info.message);
558 
559         ex.setOwnerTxnIds(getTxnIds(info.owners));
560         ex.setWaiterTxnIds(getTxnIds(info.waiters));
561         ex.setTimeoutMillis(timeout);
562 
563         return ex;
564     }
565 
566     static class TimeoutInfo {
567         final String message;
568         final Set<LockInfo> owners;
569         final List<LockInfo> waiters;
570 
TimeoutInfo(final String message, final Set<LockInfo> owners, final List<LockInfo> waiters)571         TimeoutInfo(final String message,
572                     final Set<LockInfo> owners,
573                     final List<LockInfo> waiters) {
574             this.message = message;
575             this.owners = owners;
576             this.waiters = waiters;
577         }
578     }
579 
580     /**
581      * Create a informative lock or txn timeout message.
582      */
getTimeoutInfo( boolean isLockNotTxnTimeout, Locker locker, long lsn, LockType type, LockGrantType grantType, Lock useLock, long timeout, long start, long now, DatabaseImpl database)583     abstract TimeoutInfo getTimeoutInfo(
584         boolean isLockNotTxnTimeout,
585         Locker locker,
586         long lsn,
587         LockType type,
588         LockGrantType grantType,
589         Lock useLock,
590         long timeout,
591         long start,
592         long now,
593         DatabaseImpl database);
594 
595     /**
596      * Do the real work of creating an lock or txn timeout message.
597      */
getTimeoutInfoInternal( boolean isLockNotTxnTimeout, Locker locker, long lsn, LockType type, LockGrantType grantType, Lock useLock, long timeout, long start, long now, DatabaseImpl database)598     TimeoutInfo getTimeoutInfoInternal(
599         boolean isLockNotTxnTimeout,
600         Locker locker,
601         long lsn,
602         LockType type,
603         LockGrantType grantType,
604         Lock useLock,
605         long timeout,
606         long start,
607         long now,
608         DatabaseImpl database) {
609 
610         /*
611          * Because we're accessing parts of the lock, need to have protected
612          * access to the lock table because things can be changing out from
613          * underneath us.  This is a big hammer to grab for so long while we
614          * traverse the graph, but it's only when we have a deadlock and we're
615          * creating a debugging message.
616          *
617          * The alternative would be to handle ConcurrentModificationExceptions
618          * and retry until none of them happen.
619          */
620         if (lockTableDump) {
621             System.out.println("++++++++++ begin lock table dump ++++++++++");
622             for (int i = 0; i < nLockTables; i++) {
623                 boolean success = false;
624                 for (int j = 0; j < 3 && !success; j++) {
625                     try {
626                         StringBuilder sb = new StringBuilder();
627                         dumpToStringNoLatch(sb, i);
628                         System.out.println(sb.toString());
629                         success = true;
630                         break; // for j...
631                     } catch (ConcurrentModificationException CME) {
632                         continue;
633                     }
634                 }
635                 if (!success) {
636                     System.out.println("Couldn't dump locktable " + i);
637                 }
638             }
639             System.out.println("++++++++++ end lock table dump ++++++++++");
640         }
641 
642         StringBuilder sb = new StringBuilder();
643         sb.append(isLockNotTxnTimeout ? "Lock" : "Transaction");
644         sb.append(" expired. Locker ").append(locker);
645         sb.append(": waited for lock");
646 
647         if (database != null) {
648             sb.append(" on database=").append(database.getDebugName());
649         }
650         sb.append(" LockAddr:").append(System.identityHashCode(useLock));
651         sb.append(" LSN=").append(DbLsn.getNoFormatString(lsn));
652         sb.append(" type=").append(type);
653         sb.append(" grant=").append(grantType);
654         sb.append(" timeoutMillis=").append(timeout);
655         sb.append(" startTime=").append(start);
656         sb.append(" endTime=").append(now);
657         Set<LockInfo> owners = useLock.getOwnersClone();
658         List<LockInfo> waiters = useLock.getWaitersListClone();
659         sb.append("\nOwners: ").append(owners);
660         sb.append("\nWaiters: ").append(waiters).append("\n");
661         StringBuilder deadlockInfo = findDeadlock(useLock, locker);
662         if (deadlockInfo != null) {
663             sb.append(deadlockInfo);
664         }
665         return new TimeoutInfo(sb.toString(), owners, waiters);
666     }
667 
getTxnIds(Collection<LockInfo> c)668     private long[] getTxnIds(Collection<LockInfo> c) {
669         long[] ret = new long[c.size()];
670         Iterator<LockInfo> iter = c.iterator();
671         int i = 0;
672         while (iter.hasNext()) {
673             LockInfo info = iter.next();
674             ret[i++] = info.getLocker().getId();
675         }
676 
677         return ret;
678     }
679 
680     /**
681      * This method should always be called instead of explicitly creating
682      * TransactionTimeoutException, to ensure that je.lock.oldLockExceptions is
683      * enforced.
684      */
685     @SuppressWarnings("deprecation")
newTxnTimeoutException(Locker locker, String msg)686     private LockConflictException newTxnTimeoutException(Locker locker,
687                                                          String msg) {
688         return oldLockExceptions ?
689             new DeadlockException(locker, msg) :
690             new TransactionTimeoutException(locker, msg);
691     }
692 
693     /**
694      * This method should always be called instead of explicitly creating
695      * LockTimeoutException, to ensure that je.lock.oldLockExceptions is
696      * enforced.
697      */
newLockTimeoutException(Locker locker, String msg)698     private LockConflictException newLockTimeoutException(Locker locker,
699                                                           String msg) {
700         return oldLockExceptions ?
701             new DeadlockException(locker, msg) :
702             new LockTimeoutException(locker, msg);
703     }
704 
705     /**
706      * This method should always be called instead of explicitly creating
707      * LockNotAvailableException, to ensure that je.lock.oldLockExceptions is
708      * enforced.
709      */
newLockNotAvailableException(Locker locker, String msg)710     LockConflictException newLockNotAvailableException(Locker locker,
711                                                        String msg) {
712         return oldLockExceptions ?
713             new LockNotGrantedException(locker, msg) :
714             new LockNotAvailableException(locker, msg);
715     }
716 
717     /**
718      * Release a lock and possibly notify any waiters that they have been
719      * granted the lock.
720      *
721      * @param lsn The LSN of the lock to release.
722      *
723      * @return true if the lock is released successfully, false if
724      * the lock is not currently being held.
725      */
release(long lsn, Locker locker)726     public boolean release(long lsn, Locker locker)
727         throws DatabaseException {
728 
729         synchronized (locker) {
730             Set<Locker> newOwners =
731                 releaseAndFindNotifyTargets(lsn, locker);
732 
733             if (newOwners == null) {
734                 return false;
735             }
736 
737             if (newOwners.size() > 0) {
738 
739                 /*
740                  * There is a new set of owners and/or there are restart
741                  * waiters that should be notified.
742                  */
743                 Iterator<Locker> iter = newOwners.iterator();
744 
745                 while (iter.hasNext()) {
746                     Locker lockerToNotify = iter.next();
747 
748                     /* Use notifyAll to support multiple threads per txn. */
749                     synchronized (lockerToNotify) {
750                         lockerToNotify.notifyAll();
751                     }
752 
753                     assert EnvironmentImpl.maybeForceYield();
754                 }
755             }
756 
757             return true;
758         }
759     }
760 
761     /**
762      * Release the lock, and return the set of new owners to notify, if any.
763      *
764      * @return
765      * null if the lock does not exist or the given locker was not the owner,
766      * a non-empty set if owners should be notified after releasing,
767      * an empty set if no notification is required.
768      */
releaseAndFindNotifyTargets(long lsn, Locker locker)769     abstract Set<Locker> releaseAndFindNotifyTargets(long lsn,
770                                                      Locker locker)
771         throws DatabaseException;
772 
773     /**
774      * Do the real work of releaseAndFindNotifyTargets
775      */
releaseAndFindNotifyTargetsInternal(long lsn, Locker locker, int lockTableIndex)776     Set<Locker> releaseAndFindNotifyTargetsInternal(long lsn,
777                                                     Locker locker,
778                                                     int lockTableIndex) {
779         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
780         Lock useLock = lockTable.get(lsn);
781         if (useLock == null) {
782             useLock = lockTable.get(Long.valueOf(lsn));
783         }
784 
785         if (useLock == null) {
786             /* Lock doesn't exist. */
787             return null;
788         }
789 
790         Set<Locker> lockersToNotify =
791             useLock.release(locker, memoryBudget, lockTableIndex);
792         if (lockersToNotify == null) {
793             /* Not owner. */
794             return null;
795         }
796 
797         /* If it's not in use at all, remove it from the lock table. */
798         if ((useLock.nWaiters() == 0) &&
799             (useLock.nOwners() == 0)) {
800             lockTables[lockTableIndex].remove(lsn);
801             if (useLock.isThin()) {
802                 memoryBudget.updateLockMemoryUsage
803                     (REMOVE_TOTAL_THINLOCKIMPL_OVERHEAD, lockTableIndex);
804             } else {
805                 memoryBudget.updateLockMemoryUsage
806                     (REMOVE_TOTAL_LOCKIMPL_OVERHEAD, lockTableIndex);
807             }
808         }
809 
810         return lockersToNotify;
811     }
812 
813     /**
814      * Demote a lock from write to read. Call back to the owning locker to
815      * move this to its read collection.
816      * @param lsn The lock to release.
817      * @param locker
818      */
demote(long lsn, Locker locker)819     abstract void demote(long lsn, Locker locker)
820         throws DatabaseException;
821 
822     /**
823      * Do the real work of demote.
824      */
demoteInternal(long lsn, Locker locker, int lockTableIndex)825     void demoteInternal(long lsn, Locker locker, int lockTableIndex) {
826         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
827         Lock useLock = lockTable.get(Long.valueOf(lsn));
828         /* Lock may or may not be currently held. */
829         if (useLock != null) {
830             useLock.demote(locker);
831             locker.moveWriteToReadLock(lsn, useLock);
832         }
833     }
834 
835     /**
836      * Test the status of the lock on LSN.  If any transaction holds any
837      * lock on it, true is returned.  If no transaction holds a lock on it,
838      * false is returned.
839      *
840      * This method is only used by unit tests.
841      *
842      * @param lsn The LSN to check.
843      * @return true if any transaction holds any lock on the LSN. false
844      * if no lock is held by any transaction.
845      */
isLocked(Long lsn)846     abstract boolean isLocked(Long lsn)
847         throws DatabaseException;
848 
849     /**
850      * Do the real work of isLocked.
851      */
isLockedInternal(Long lsn, int lockTableIndex)852     boolean isLockedInternal(Long lsn, int lockTableIndex) {
853 
854         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
855         Lock entry = lockTable.get(lsn);
856         if (entry == null) {
857             return false;
858         }
859 
860         return entry.nOwners() != 0;
861     }
862 
863     /**
864      * Return true if this locker owns this a lock of this type on given node.
865      *
866      * This method is only used by unit tests.
867      */
isOwner(Long lsn, Locker locker, LockType type)868     abstract boolean isOwner(Long lsn, Locker locker, LockType type)
869         throws DatabaseException;
870 
871     /**
872      * Do the real work of isOwner.
873      */
isOwnerInternal(Long lsn, Locker locker, LockType type, int lockTableIndex)874     boolean isOwnerInternal(Long lsn,
875                             Locker locker,
876                             LockType type,
877                             int lockTableIndex) {
878 
879         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
880         Lock entry = lockTable.get(lsn);
881         if (entry == null) {
882             return false;
883         }
884 
885         return entry.isOwner(locker, type);
886     }
887 
888     /**
889      * Return true if this locker is waiting on this lock.
890      *
891      * This method is only used by unit tests.
892      */
isWaiter(Long lsn, Locker locker)893     abstract boolean isWaiter(Long lsn, Locker locker)
894         throws DatabaseException;
895 
896     /**
897      * Do the real work of isWaiter.
898      */
isWaiterInternal(Long lsn, Locker locker, int lockTableIndex)899     boolean isWaiterInternal(Long lsn,
900                              Locker locker,
901                              int lockTableIndex) {
902 
903         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
904         Lock entry = lockTable.get(lsn);
905         if (entry == null) {
906             return false;
907         }
908 
909         return entry.isWaiter(locker);
910     }
911 
912     /**
913      * Return the number of waiters for this lock.
914      */
nWaiters(Long lsn)915     abstract int nWaiters(Long lsn)
916         throws DatabaseException;
917 
918     /**
919      * Do the real work of nWaiters.
920      */
nWaitersInternal(Long lsn, int lockTableIndex)921     int nWaitersInternal(Long lsn, int lockTableIndex) {
922 
923         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
924         Lock entry = lockTable.get(lsn);
925         if (entry == null) {
926             return -1;
927         }
928 
929         return entry.nWaiters();
930     }
931 
932     /**
933      * Return the number of owners of this lock.
934      */
nOwners(Long lsn)935     abstract int nOwners(Long lsn)
936         throws DatabaseException;
937 
938     /**
939      * Do the real work of nWaiters.
940      */
nOwnersInternal(Long lsn, int lockTableIndex)941     int nOwnersInternal(Long lsn, int lockTableIndex) {
942 
943         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
944         Lock entry = lockTable.get(lsn);
945         if (entry == null) {
946             return -1;
947         }
948 
949         return entry.nOwners();
950     }
951 
952     /**
953      * @return the transaction that owns the write lock for this
954      */
getWriteOwnerLocker(Long lsn)955     abstract Locker getWriteOwnerLocker(Long lsn)
956         throws DatabaseException;
957 
958     /**
959      * Do the real work of getWriteOwnerLocker.
960      */
getWriteOwnerLockerInternal(Long lsn, int lockTableIndex)961     Locker getWriteOwnerLockerInternal(Long lsn, int lockTableIndex) {
962         Map<Long,Lock> lockTable = lockTables[lockTableIndex];
963         Lock lock = lockTable.get(lsn);
964         if (lock == null) {
965             return null;
966         } else if (lock.nOwners() > 1) {
967             /* not a write lock */
968             return null;
969         } else {
970             return lock.getWriteOwnerLocker();
971         }
972     }
973 
974     /*
975      * Check if we got ownership while we were waiting.  If we didn't get
976      * ownership, and we timed out, remove this locker from the set of
977      * waiters. Do this in a critical section to prevent any orphaning of the
978      * lock -- we must be in a critical section between the time that we check
979      * ownership and when we flush any waiters (SR #10103)
980      * @return true if you are the owner.
981      */
validateOwnership(Long lsn, Locker locker, LockType type, boolean flushFromWaiters, MemoryBudget mb)982     abstract boolean validateOwnership(Long lsn,
983                                        Locker locker,
984                                        LockType type,
985                                        boolean flushFromWaiters,
986                                        MemoryBudget mb)
987         throws DatabaseException;
988 
989     /*
990      * Do the real work of validateOwnershipInternal.
991      */
validateOwnershipInternal(Long lsn, Locker locker, LockType type, boolean flushFromWaiters, MemoryBudget mb, int lockTableIndex)992     boolean validateOwnershipInternal(Long lsn,
993                                       Locker locker,
994                                       LockType type,
995                                       boolean flushFromWaiters,
996                                       MemoryBudget mb,
997                                       int lockTableIndex) {
998         if (isOwnerInternal(lsn, locker, type, lockTableIndex)) {
999             return true;
1000         }
1001 
1002         if (flushFromWaiters) {
1003             Lock entry = lockTables[lockTableIndex].get(lsn);
1004             if (entry != null) {
1005                 entry.flushWaiter(locker, mb, lockTableIndex);
1006             }
1007         }
1008         return false;
1009     }
1010 
stealLock(Long lsn, Locker locker, LockType lockType)1011     public abstract LockAttemptResult stealLock(Long lsn,
1012                                                    Locker locker,
1013                                                    LockType lockType)
1014         throws DatabaseException;
1015 
stealLockInternal(Long lsn, Locker locker, LockType lockType, int lockTableIndex)1016     protected LockAttemptResult stealLockInternal(Long lsn,
1017                                                   Locker locker,
1018                                                   LockType lockType,
1019                                                   int lockTableIndex)
1020         throws DatabaseException {
1021 
1022         Lock entry = lockTables[lockTableIndex].get(lsn);
1023         assert entry != null : "Lock " + DbLsn.getNoFormatString(lsn) +
1024                 " for txn " + locker.getId() + " does not exist";
1025 
1026         /*
1027          * Note that flushWaiter may do nothing, because the lock may have been
1028          * granted to our locker after the prior call to attemptLock and before
1029          * the call to this method.
1030          */
1031         entry.flushWaiter(locker, memoryBudget, lockTableIndex);
1032 
1033         /* Remove all owners except for our owner. */
1034         entry.stealLock(locker, memoryBudget, lockTableIndex);
1035 
1036         /*
1037          * The lock attempt normally succeeds, but can fail if the lock holder
1038          * is non-preemptable.
1039          */
1040         return attemptLockInternal
1041             (lsn, locker, lockType, false /*nonBlockingRequest*/,
1042              false /*jumpAheadOfWaiters*/, lockTableIndex);
1043     }
1044 
1045     /**
1046      * Called when a ThreadLocker is created.
1047      */
registerThreadLocker(final ThreadLocker locker)1048     public void registerThreadLocker(final ThreadLocker locker) {
1049         if (threadLockers == null) {
1050             return;
1051         }
1052         final Thread thread = Thread.currentThread();
1053         final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
1054         if (set != null) {
1055             final boolean added = set.add(locker);
1056             assert added;
1057         } else {
1058             threadLockers.put(thread, new TinyHashSet(locker));
1059         }
1060     }
1061 
1062     /**
1063      * Called when a ThreadLocker is closed.
1064      */
unregisterThreadLocker(final ThreadLocker locker)1065     public void unregisterThreadLocker(final ThreadLocker locker) {
1066         if (threadLockers == null) {
1067             return;
1068         }
1069         final Thread thread = Thread.currentThread();
1070         final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
1071         assert set != null;
1072         final boolean removed = set.remove(locker);
1073         assert removed;
1074         if (threadLockers.size() == 0) {
1075             threadLockers.remove(thread);
1076         }
1077     }
1078 
1079     /**
1080      * Returns an iterator over all thread lockers for the given thread, or
1081      * an empty iterator if none.
1082      */
getThreadLockers(final Thread thread)1083     public Iterator<ThreadLocker> getThreadLockers(final Thread thread) {
1084         if (threadLockers == null) {
1085             return EMPTY_THREAD_LOCKERS.iterator();
1086         }
1087         final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
1088         if (set == null) {
1089             return EMPTY_THREAD_LOCKERS.iterator();
1090         }
1091         return set.iterator();
1092     }
1093 
1094     /**
1095      * Statistics
1096      */
lockStat(StatsConfig config)1097     public LockStats lockStat(StatsConfig config)
1098         throws DatabaseException {
1099 
1100         StatGroup latchStats = new StatGroup("Locktable latches",
1101                                              "Shows lock table contention");
1102         for (int i = 0; i < nLockTables; i++) {
1103             latchStats.addAll(lockTableLatches[i].getStats());
1104         }
1105 
1106         /* Dump info about the lock table. */
1107         StatGroup tableStats =
1108             new StatGroup("Locktable",
1109                           "The types of locks held in the lock table");
1110         if (!config.getFast()) {
1111             dumpLockTable(tableStats, false /*clear*/);
1112         }
1113 
1114         return new LockStats(stats.cloneGroup(config.getClear()),
1115                              latchStats.cloneGroup(config.getClear()),
1116                              tableStats.cloneGroup(config.getClear()));
1117     }
1118 
loadStats(StatsConfig config)1119     public StatGroup loadStats(StatsConfig config) {
1120         StatGroup copyStats = stats.cloneGroup(config.getClear());
1121 
1122         StatGroup latchStats = new StatGroup("Locktable latches",
1123                                              "Shows lock table contention");
1124         for (int i = 0; i < nLockTables; i++) {
1125             latchStats.addAll(lockTableLatches[i].getStats());
1126             if (config.getClear()) {
1127                 lockTableLatches[i].clearStats();
1128             }
1129         }
1130         /* Add all the latch stats to the whole stats group. */
1131         copyStats.addAll(latchStats);
1132 
1133         StatGroup tableStats =
1134             new StatGroup("Locktable",
1135                           "The types of locks held in the lock table");
1136         if (!config.getFast()) {
1137             dumpLockTable(tableStats, config.getClear());
1138         }
1139         /* Add all the lock table stats to the whole stats group. */
1140         copyStats.addAll(tableStats);
1141 
1142         return copyStats;
1143     }
1144 
1145     /**
1146      * Dump the lock table to the lock stats.
1147      */
dumpLockTable(StatGroup tableStats, boolean clear)1148     abstract void dumpLockTable(StatGroup tableStats, boolean clear)
1149         throws DatabaseException;
1150 
1151     /**
1152      * Do the real work of dumpLockTableInternal.
1153      */
dumpLockTableInternal(StatGroup tableStats, int i, boolean clear)1154     void dumpLockTableInternal(StatGroup tableStats, int i, boolean clear) {
1155         StatGroup oneTable = new StatGroup("Single lock table",
1156                                            "Temporary stat group");
1157 
1158         IntStat totalLocks = new IntStat(oneTable, LOCK_TOTAL);
1159         IntStat waiters = new IntStat(oneTable, LOCK_WAITERS);
1160         IntStat owners = new IntStat(oneTable, LOCK_OWNERS);
1161         IntStat readLocks = new IntStat(oneTable, LOCK_READ_LOCKS);
1162         IntStat writeLocks = new IntStat(oneTable, LOCK_WRITE_LOCKS);
1163 
1164         Map<Long, Lock> lockTable = lockTables[i];
1165         totalLocks.add(lockTable.size());
1166 
1167         for (Lock lock : lockTable.values()) {
1168             waiters.add(lock.nWaiters());
1169             owners.add(lock.nOwners());
1170 
1171             /* Go through all the owners for a lock. */
1172             for (LockInfo info : lock.getOwnersClone()) {
1173                 if (info.getLockType().isWriteLock()) {
1174                     writeLocks.increment();
1175                 } else {
1176                     readLocks.increment();
1177                 }
1178             }
1179         }
1180         tableStats.addAll(oneTable);
1181     }
1182 
1183     /**
1184      * Debugging
1185      */
dump()1186     public void dump()
1187         throws DatabaseException {
1188 
1189         System.out.println(dumpToString());
1190     }
1191 
dumpToString()1192     public String dumpToString()
1193         throws DatabaseException {
1194 
1195         StringBuilder sb = new StringBuilder();
1196         for (int i = 0; i < nLockTables; i++) {
1197             lockTableLatches[i].acquireExclusive();
1198             try {
1199                 dumpToStringNoLatch(sb, i);
1200             } finally {
1201                 lockTableLatches[i].release();
1202             }
1203         }
1204         return sb.toString();
1205     }
1206 
dumpToStringNoLatch(StringBuilder sb, int whichTable)1207     private void dumpToStringNoLatch(StringBuilder sb, int whichTable) {
1208         Map<Long,Lock> lockTable = lockTables[whichTable];
1209         Iterator<Map.Entry<Long,Lock>> entries =
1210             lockTable.entrySet().iterator();
1211 
1212         while (entries.hasNext()) {
1213             Map.Entry<Long,Lock> entry = entries.next();
1214             Long lsn = entry.getKey();
1215             Lock lock = entry.getValue();
1216             sb.append("---- LSN: ").
1217                append(DbLsn.getNoFormatString(lsn)).
1218                append("----\n");
1219             sb.append(lock);
1220             sb.append('\n');
1221         }
1222     }
1223 
findDeadlock(Lock lock, Locker rootLocker)1224     private StringBuilder findDeadlock(Lock lock, Locker rootLocker) {
1225 
1226         Set<Locker> ownerSet = new HashSet<Locker>();
1227         ownerSet.add(rootLocker);
1228         StringBuilder ret = findDeadlock1(ownerSet, lock, rootLocker);
1229         if (ret != null) {
1230             return ret;
1231         } else {
1232             return null;
1233         }
1234     }
1235 
findDeadlock1(Set<Locker> ownerSet, Lock lock, Locker rootLocker)1236     private StringBuilder findDeadlock1(Set<Locker> ownerSet,
1237                                        Lock lock,
1238                                        Locker rootLocker) {
1239 
1240         /*
1241          * A ConcurrentModificationException may be thrown by getOwnersClone
1242          * when another thread changes the owners for a lock that happens to be
1243          * in another lock table (not the lock requested in this thread). This
1244          * is because deadlock detection is not "real" yet, we don't freeze all
1245          * locking activities, and we only synchronize the lock table for the
1246          * original lock. If retrying doesn't work around the problem, we just
1247          * give up. This is acceptable because deadlock detection is currently
1248          * used only to add debugging information to the exception.
1249          * TODO:
1250          * To implement true deadlock detection accurately we may need to
1251          * freeze all locking activities by synchronizing all lock tables,
1252          * synchronize all owners, make fields (waitingFor) volatile, etc.
1253          */
1254         Iterator<LockInfo> ownerIter = null;
1255         for (int i = 0; i < 10; i += 1) {
1256             try {
1257                 ownerIter = lock.getOwnersClone().iterator();
1258                 break;
1259             } catch (ConcurrentModificationException e) {
1260                 /* continue */
1261             }
1262         }
1263         if (ownerIter == null) {
1264             return null;
1265         }
1266 
1267         /*
1268          * WARNING: Be sure not to use the locker and waitsFor locals below in
1269          * a way that requires synchronization. At this point we are already
1270          * synchronized on the rootLocker and the lock table for the original
1271          * lock. Additional synchronization on other lockers or lock table
1272          * entries could cause a mutex deadlock.
1273          *
1274          * Calling Locker.toString is safe without synchronization. However,
1275          * Lock.toString and getOwnersClone are not safe. getOwnersClone is
1276          * handled above for the recursive call to this method. Below we are
1277          * careful not to call Lock.toString and instead use identityHashCode.
1278          */
1279         while (ownerIter.hasNext()) {
1280             LockInfo info = ownerIter.next();
1281             Locker locker = info.getLocker();
1282             Lock waitsFor = locker.getWaitingFor();
1283             if (ownerSet.contains(locker) ||
1284                 locker == rootLocker) {
1285                 /* Found a cycle. */
1286                 StringBuilder ret = new StringBuilder();
1287                 ret.append("Transaction ").append(locker.toString());
1288                 ret.append(" owns LockAddr:").
1289                     append(System.identityHashCode(lock));
1290                 ret.append(" ").append(info).append("\n");
1291                 ret.append("Transaction ").append(locker.toString());
1292                 ret.append(" waits for");
1293                 if (waitsFor == null) {
1294                     ret.append(" nothing");
1295                 } else {
1296                     ret.append(" LockAddr:");
1297                     ret.append(System.identityHashCode(waitsFor));
1298                 }
1299                 ret.append("\n");
1300                 return ret;
1301             }
1302             if (waitsFor != null) {
1303                 ownerSet.add(locker);
1304                 StringBuilder sb = findDeadlock1(ownerSet, waitsFor,
1305                                                 rootLocker);
1306                 if (sb != null) {
1307                     String waitInfo =
1308                         "Transaction " + locker + " waits for  LockAddr:" +
1309                         System.identityHashCode(waitsFor) + "\n";
1310                     sb.insert(0, waitInfo);
1311                     return sb;
1312                 }
1313                 ownerSet.remove(locker); // is this necessary?
1314             }
1315         }
1316 
1317         return null;
1318     }
1319 }
1320