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