001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: Locker.java,v 1.101.2.6 2008/02/21 08:09:03 chao Exp $
007: */
008:
009: package com.sleepycat.je.txn;
010:
011: import java.util.HashMap;
012: import java.util.HashSet;
013: import java.util.Hashtable;
014: import java.util.Iterator;
015: import java.util.Map;
016: import java.util.Set;
017:
018: import com.sleepycat.je.Database;
019: import com.sleepycat.je.DatabaseException;
020: import com.sleepycat.je.DbInternal;
021: import com.sleepycat.je.DeadlockException;
022: import com.sleepycat.je.LockNotGrantedException;
023: import com.sleepycat.je.LockStats;
024: import com.sleepycat.je.OperationStatus;
025: import com.sleepycat.je.dbi.CursorImpl;
026: import com.sleepycat.je.dbi.DatabaseImpl;
027: import com.sleepycat.je.dbi.EnvironmentImpl;
028: import com.sleepycat.je.tree.BIN;
029: import com.sleepycat.je.tree.BINReference;
030: import com.sleepycat.je.tree.Key;
031:
032: /**
033: * Locker instances are JE's route to locking and transactional support. This
034: * class is the abstract base class for BasicLocker, ThreadLocker, Txn and
035: * AutoTxn. Locker instances are in fact only a transaction shell to get to
036: * the lock manager, and don't guarantee transactional semantics. Txn and
037: * AutoTxn instances are both truely transactional, but have different ending
038: * behaviors.
039: */
040: public abstract class Locker {
041: private static final String DEBUG_NAME = Locker.class.getName();
042: protected EnvironmentImpl envImpl;
043: protected LockManager lockManager;
044:
045: protected long id; // transaction id
046: protected boolean readUncommittedDefault; // read-uncommitted is default
047:
048: /* Timeouts */
049: protected boolean defaultNoWait; // true for non-blocking
050: protected long lockTimeOutMillis; // timeout period for lock, in ms
051: private long txnTimeOutMillis; // timeout period for txns, in ms
052: private long txnStartMillis; // for txn timeout determination
053:
054: private Lock waitingFor; // The lock that this txn is
055: // waiting for.
056:
057: /*
058: * DeleteInfo refers to BINReferences that should be sent to the
059: * INCompressor for asynchronous compressing after the transaction ends.
060: */
061: protected Map deleteInfo;
062:
063: /*
064: * To support handle lock transfers, each txn keeps maps handle locks to
065: * database handles. This is maintained as a map where the key is the
066: * handle lock id and the value is a set of database handles that
067: * correspond to that handle lock. This is a 1 - many relationship because
068: * a single handle lock can cover multiple database handles opened by the
069: * same transaction.
070: */
071: protected Map handleLockToHandleMap; // 1-many, used for commits
072: protected Map handleToHandleLockMap; // 1-1, used for aborts
073:
074: /**
075: * The thread that created this locker. Used for debugging, and by the
076: * ThreadLocker subclass. Note that thread may be null if the Locker is
077: * instantiated by reading the log.
078: */
079: protected Thread thread;
080:
081: /**
082: * Create a locker id. This constructor is called very often, so it should
083: * be as streamlined as possible.
084: *
085: * @param lockManager lock manager for this environment
086: * @param readUncommittedDefault if true, this transaction does
087: * read-uncommitted by default
088: * @param noWait if true, non-blocking lock requests are used.
089: */
090: public Locker(EnvironmentImpl envImpl,
091: boolean readUncommittedDefault, boolean noWait)
092: throws DatabaseException {
093:
094: TxnManager txnManager = envImpl.getTxnManager();
095: this .id = generateId(txnManager);
096: this .envImpl = envImpl;
097: lockManager = txnManager.getLockManager();
098: this .readUncommittedDefault = readUncommittedDefault;
099: this .waitingFor = null;
100:
101: /* get the default lock timeout. */
102: defaultNoWait = noWait;
103: lockTimeOutMillis = envImpl.getLockTimeout();
104:
105: /*
106: * Check the default txn timeout. If non-zero, remember the txn start
107: * time.
108: */
109: txnTimeOutMillis = envImpl.getTxnTimeout();
110:
111: if (txnTimeOutMillis != 0) {
112: txnStartMillis = System.currentTimeMillis();
113: } else {
114: txnStartMillis = 0;
115: }
116:
117: /* Save the thread used to create the locker. */
118: thread = Thread.currentThread();
119:
120: /*
121: * Do lazy initialization of deleteInfo and handle lock maps, to
122: * conserve memory.
123: */
124: }
125:
126: /**
127: * For reading from the log.
128: */
129: Locker() {
130: }
131:
132: /**
133: * A Locker has to generate its next id. Some subtypes, like BasicLocker,
134: * have a single id for all instances because they are never used for
135: * recovery. Other subtypes ask the txn manager for an id.
136: */
137: protected abstract long generateId(TxnManager txnManager);
138:
139: /**
140: * @return the transaction's id.
141: */
142: public long getId() {
143: return id;
144: }
145:
146: /**
147: * @return the default no-wait (non-blocking) setting.
148: */
149: public boolean getDefaultNoWait() {
150: return defaultNoWait;
151: }
152:
153: /**
154: * Get the lock timeout period for this transaction, in milliseconds
155: */
156: public synchronized long getLockTimeout() {
157: return lockTimeOutMillis;
158: }
159:
160: /**
161: * Set the lock timeout period for any locks in this transaction,
162: * in milliseconds.
163: *
164: * @param timeout The timeout value for the transaction lifetime, in
165: * milliseconds. A value of 0 disables timeouts for the transaction.
166: *
167: * @throws IllegalArgumentException If the value of timeout is negative.
168: */
169: public synchronized void setLockTimeout(long timeout) {
170:
171: if (timeout < 0) {
172: throw new IllegalArgumentException(
173: "the timeout value cannot be negative");
174: }
175:
176: lockTimeOutMillis = timeout;
177: }
178:
179: /**
180: * Set the timeout period for this transaction, in milliseconds.
181: *
182: * @param timeout The timeout value for the transaction lifetime, in
183: * milliseconds. A value of 0 disables timeouts for the transaction.
184: *
185: * @throws IllegalArgumentException If the value of timeout is negative.
186: */
187: public synchronized void setTxnTimeout(long timeout) {
188:
189: if (timeout < 0) {
190: throw new IllegalArgumentException(
191: "the timeout value cannot be negative");
192: }
193:
194: txnTimeOutMillis = timeout;
195: if (txnTimeOutMillis != 0) {
196: txnStartMillis = System.currentTimeMillis();
197: } else {
198: txnStartMillis = 0;
199: }
200: }
201:
202: /**
203: * @return true if transaction was created with read-uncommitted as a
204: * default.
205: */
206: public boolean isReadUncommittedDefault() {
207: return readUncommittedDefault;
208: }
209:
210: Lock getWaitingFor() {
211: return waitingFor;
212: }
213:
214: void setWaitingFor(Lock lock) {
215: waitingFor = lock;
216: }
217:
218: /**
219: * Set the state of a transaction to ONLY_ABORTABLE.
220: */
221: void setOnlyAbortable() {
222: /* no-op unless Txn. */
223: }
224:
225: protected abstract void checkState(boolean ignoreCalledByAbort)
226: throws DatabaseException;
227:
228: /*
229: * Obtain and release locks.
230: */
231:
232: /**
233: * Abstract method to a blocking or non-blocking lock of the given type on
234: * the given nodeId. Unlike the lock() method, this method does not throw
235: * LockNotGrantedException and can therefore be used by nonBlockingLock to
236: * probe for a lock without the overhead of an exception stack trace.
237: *
238: * @param nodeId is the node to lock.
239: *
240: * @param lockType is the type of lock to request.
241: *
242: * @param noWait is true to override the defaultNoWait setting. If true,
243: * or if defaultNoWait is true, throws LockNotGrantedException if the lock
244: * cannot be granted without waiting.
245: *
246: * @param database is the database containing nodeId.
247: *
248: * @throws DeadlockException if acquiring a blocking lock would result in a
249: * deadlock.
250: */
251: abstract LockResult lockInternal(long nodeId, LockType lockType,
252: boolean noWait, DatabaseImpl database)
253: throws DeadlockException, DatabaseException;
254:
255: /**
256: * Request a blocking or non-blocking lock of the given type on the given
257: * nodeId.
258: *
259: * @param nodeId is the node to lock.
260: *
261: * @param lockType is the type of lock to request.
262: *
263: * @param noWait is true to override the defaultNoWait setting. If true,
264: * or if defaultNoWait is true, throws LockNotGrantedException if the lock
265: * cannot be granted without waiting.
266: *
267: * @param database is the database containing nodeId.
268: *
269: * @throws LockNotGrantedException if a non-blocking lock was denied.
270: *
271: * @throws DeadlockException if acquiring a blocking lock would result in a
272: * deadlock.
273: */
274: public LockResult lock(long nodeId, LockType lockType,
275: boolean noWait, DatabaseImpl database)
276: throws LockNotGrantedException, DeadlockException,
277: DatabaseException {
278:
279: LockResult result = lockInternal(nodeId, lockType, noWait,
280: database);
281:
282: if (result.getLockGrant() == LockGrantType.DENIED) {
283: /* DENIED can only be returned for a non-blocking lock. */
284: throw new LockNotGrantedException(
285: "Non-blocking lock was denied.");
286: } else {
287: return result;
288: }
289: }
290:
291: /**
292: * Request a non-blocking lock of the given type on the given nodeId.
293: *
294: * <p>Unlike lock(), this method returns LockGrantType.DENIED if the lock
295: * is denied rather than throwing LockNotGrantedException. This method
296: * should therefore not be used as the final lock for a user operation,
297: * since in that case LockNotGrantedException should be thrown for a denied
298: * lock. It is normally used only to probe for a lock, and other recourse
299: * is taken if the lock is denied.</p>
300: *
301: * @param nodeId is the node to lock.
302: *
303: * @param lockType is the type of lock to request.
304: *
305: * @param database is the database containing nodeId.
306: */
307: public LockResult nonBlockingLock(long nodeId, LockType lockType,
308: DatabaseImpl database) throws DatabaseException {
309:
310: return lockInternal(nodeId, lockType, true, database);
311: }
312:
313: /**
314: * Release the lock on this LN and remove from the transaction's owning
315: * set.
316: */
317: public void releaseLock(long nodeId) throws DatabaseException {
318:
319: lockManager.release(nodeId, this );
320: removeLock(nodeId);
321: }
322:
323: /**
324: * Revert this lock from a write lock to a read lock.
325: */
326: public void demoteLock(long nodeId) throws DatabaseException {
327:
328: /*
329: * If successful, the lock manager will call back to the transaction
330: * and adjust the location of the lock in the lock collection.
331: */
332: lockManager.demote(nodeId, this );
333: }
334:
335: /**
336: * Returns whether this locker is transactional.
337: */
338: public abstract boolean isTransactional();
339:
340: /**
341: * Returns whether the isolation level of this locker is serializable.
342: */
343: public abstract boolean isSerializableIsolation();
344:
345: /**
346: * Returns whether the isolation level of this locker is read-committed.
347: */
348: public abstract boolean isReadCommittedIsolation();
349:
350: /**
351: * Returns the underlying Txn if the locker is transactional, or null if
352: * the locker is non-transactional. For a Txn-based locker, this method
353: * returns 'this'. For a BuddyLocker, this method may returns the buddy.
354: */
355: public abstract Txn getTxnLocker();
356:
357: /**
358: * Creates a fresh non-transactional locker, while retaining any
359: * transactional locks held by this locker. This method is called when the
360: * cursor for this locker is cloned.
361: *
362: * <p>In general, transactional lockers return 'this' when this method is
363: * called, while non-transactional lockers return a new instance.</p>
364: */
365: public abstract Locker newNonTxnLocker() throws DatabaseException;
366:
367: /**
368: * Releases any non-transactional locks held by this locker. This method
369: * is called when the cursor moves to a new position or is closed.
370: *
371: * <p>In general, transactional lockers do nothing when this method is
372: * called, while non-transactional lockers release all locks as if
373: * operationEnd were called.</p>
374: */
375: public abstract void releaseNonTxnLocks() throws DatabaseException;
376:
377: /**
378: * Returns whether this locker can share locks with the given locker.
379: *
380: * <p>All lockers share locks with a BuddyLocker whose buddy is this
381: * locker. To support BuddyLocker when overriding this method, always
382: * return true if this implementation (super.sharesLocksWith(...)) returns
383: * true.</p>
384: */
385: public boolean sharesLocksWith(Locker other) {
386: if (other instanceof BuddyLocker) {
387: BuddyLocker buddy = (BuddyLocker) other;
388: return buddy.getBuddy() == this ;
389: } else {
390: return false;
391: }
392: }
393:
394: /**
395: * The equivalent of calling operationEnd(true).
396: */
397: public abstract void operationEnd() throws DatabaseException;
398:
399: /**
400: * Different types of transactions do different things when the operation
401: * ends. Txns do nothing, AutoTxns commit or abort, and BasicLockers and
402: * ThreadLockers just release locks.
403: *
404: * @param operationOK is whether the operation succeeded, since
405: * that may impact ending behavior. (i.e for AutoTxn)
406: */
407: public abstract void operationEnd(boolean operationOK)
408: throws DatabaseException;
409:
410: /**
411: * We're at the end of an operation. Move this handle lock to the
412: * appropriate owner.
413: */
414: public abstract void setHandleLockOwner(boolean operationOK,
415: Database dbHandle, boolean dbIsClosing)
416: throws DatabaseException;
417:
418: /**
419: * A SUCCESS status equals operationOk.
420: */
421: public void operationEnd(OperationStatus status)
422: throws DatabaseException {
423:
424: operationEnd(status == OperationStatus.SUCCESS);
425: }
426:
427: /**
428: * Tell this transaction about a cursor.
429: */
430: public abstract void registerCursor(CursorImpl cursor)
431: throws DatabaseException;
432:
433: /**
434: * Remove a cursor from this txn.
435: */
436: public abstract void unRegisterCursor(CursorImpl cursor)
437: throws DatabaseException;
438:
439: /*
440: * Transactional support
441: */
442:
443: /**
444: * @return the abort LSN for this node.
445: */
446: public abstract long getAbortLsn(long nodeId)
447: throws DatabaseException;
448:
449: /**
450: * @return the WriteLockInfo for this node.
451: */
452: public abstract WriteLockInfo getWriteLockInfo(long nodeId)
453: throws DatabaseException;
454:
455: /**
456: * Database operations like remove and truncate leave behind
457: * residual DatabaseImpls that must be purged at transaction
458: * commit or abort.
459: */
460: public abstract void markDeleteAtTxnEnd(DatabaseImpl db,
461: boolean deleteAtCommit) throws DatabaseException;
462:
463: /**
464: * Add delete information, to be added to the inCompressor queue
465: * when the transaction ends.
466: */
467: public void addDeleteInfo(BIN bin, Key deletedKey)
468: throws DatabaseException {
469:
470: synchronized (this ) {
471: /* Maintain only one binRef per node. */
472: if (deleteInfo == null) {
473: deleteInfo = new HashMap();
474: }
475: Long nodeId = new Long(bin.getNodeId());
476: BINReference binRef = (BINReference) deleteInfo.get(nodeId);
477: if (binRef == null) {
478: binRef = bin.createReference();
479: deleteInfo.put(nodeId, binRef);
480: }
481: binRef.addDeletedKey(deletedKey);
482: }
483: }
484:
485: /*
486: * Manage locks owned by this transaction. Note that transactions that will
487: * be multithreaded must override these methods and provide synchronized
488: * implementations.
489: */
490:
491: /**
492: * Add a lock to set owned by this transaction.
493: */
494: abstract void addLock(Long nodeId, LockType type,
495: LockGrantType grantStatus) throws DatabaseException;
496:
497: /**
498: * @return true if this transaction created this node,
499: * for a operation with transactional semantics.
500: */
501: public abstract boolean createdNode(long nodeId)
502: throws DatabaseException;
503:
504: /**
505: * Remove the lock from the set owned by this transaction. If specified to
506: * LockManager.release, the lock manager will call this when its releasing
507: * a lock.
508: */
509: abstract void removeLock(long nodeId) throws DatabaseException;
510:
511: /**
512: * A lock is being demoted. Move it from the write collection into the read
513: * collection.
514: */
515: abstract void moveWriteToReadLock(long nodeId, Lock lock);
516:
517: /**
518: * Get lock count, for per transaction lock stats, for internal debugging.
519: */
520: public abstract LockStats collectStats(LockStats stats)
521: throws DatabaseException;
522:
523: /*
524: * Check txn timeout, if set. Called by the lock manager when blocking on a
525: * lock.
526: */
527: public boolean isTimedOut() throws DatabaseException {
528:
529: if (txnTimeOutMillis != 0) {
530: long diff = System.currentTimeMillis() - txnStartMillis;
531: if (diff > txnTimeOutMillis) {
532: return true;
533: }
534: }
535: return false;
536: }
537:
538: /* public for jca/ra/JELocalTransaction. */
539: public long getTxnTimeOut() {
540: return txnTimeOutMillis;
541: }
542:
543: long getTxnStartMillis() {
544: return txnStartMillis;
545: }
546:
547: /**
548: * Remove this Database from the protected Database handle set
549: */
550: void unregisterHandle(Database dbHandle) {
551:
552: /*
553: * handleToHandleLockMap may be null if the db handle was never really
554: * added. This might be the case because of an unregisterHandle that
555: * comes from a finally clause, where the db handle was never
556: * successfully opened.
557: */
558: if (handleToHandleLockMap != null) {
559: handleToHandleLockMap.remove(dbHandle);
560: }
561: }
562:
563: /**
564: * Remember how handle locks and handles match up.
565: */
566: public void addToHandleMaps(Long handleLockId,
567: Database databaseHandle) {
568: Set dbHandleSet = null;
569: if (handleLockToHandleMap == null) {
570:
571: /*
572: * We do lazy initialization of the maps, since they're used
573: * infrequently.
574: */
575: handleLockToHandleMap = new Hashtable();
576: handleToHandleLockMap = new Hashtable();
577: } else {
578: dbHandleSet = (Set) handleLockToHandleMap.get(handleLockId);
579: }
580:
581: if (dbHandleSet == null) {
582: dbHandleSet = new HashSet();
583: handleLockToHandleMap.put(handleLockId, dbHandleSet);
584: }
585:
586: /* Map handle lockIds -> 1 or more database handles. */
587: dbHandleSet.add(databaseHandle);
588: /* Map database handles -> handle lock id */
589: handleToHandleLockMap.put(databaseHandle, handleLockId);
590: }
591:
592: /**
593: * @return true if this txn is willing to give up the handle lock to
594: * another txn before this txn ends.
595: */
596: public boolean isHandleLockTransferrable() {
597: return true;
598: }
599:
600: /**
601: * The currentTxn passes responsiblity for this db handle lock to a txn
602: * owned by the Database object.
603: */
604: void transferHandleLockToHandle(Database dbHandle)
605: throws DatabaseException {
606:
607: /*
608: * Transfer responsiblity for this db lock from this txn to a new
609: * protector.
610: */
611: Locker holderTxn = new BasicLocker(envImpl);
612: transferHandleLock(dbHandle, holderTxn, true);
613: }
614:
615: /**
616: *
617: */
618: public void transferHandleLock(Database dbHandle,
619: Locker destLocker, boolean demoteToRead)
620: throws DatabaseException {
621:
622: /*
623: * Transfer responsiblity for dbHandle's handle lock from this txn to
624: * destLocker. If the dbHandle's databaseImpl is null, this handle
625: * wasn't opened successfully.
626: */
627: if (DbInternal.dbGetDatabaseImpl(dbHandle) != null) {
628: Long handleLockId = (Long) handleToHandleLockMap
629: .get(dbHandle);
630: if (handleLockId != null) {
631: /* We have a handle lock for this db. */
632: long nodeId = handleLockId.longValue();
633:
634: /* Move this lock to the destination txn. */
635: lockManager.transfer(nodeId, this , destLocker,
636: demoteToRead);
637:
638: /*
639: * Make the destination txn remember that it now owns this
640: * handle lock.
641: */
642: destLocker.addToHandleMaps(handleLockId, dbHandle);
643:
644: /* Take this out of the handle lock map. */
645: Set dbHandleSet = (Set) handleLockToHandleMap
646: .get(handleLockId);
647: Iterator iter = dbHandleSet.iterator();
648: while (iter.hasNext()) {
649: if (((Database) iter.next()) == dbHandle) {
650: iter.remove();
651: break;
652: }
653: }
654: if (dbHandleSet.size() == 0) {
655: handleLockToHandleMap.remove(handleLockId);
656: }
657:
658: /*
659: * This Database must remember what txn owns it's handle lock.
660: */
661: DbInternal.dbSetHandleLocker(dbHandle, destLocker);
662: }
663: }
664: }
665:
666: /*
667: * Helpers
668: */
669: public String toString() {
670: String className = getClass().getName();
671: className = className.substring(className.lastIndexOf('.') + 1);
672:
673: return Long.toString(id) + "_"
674: + ((thread == null) ? "" : thread.getName()) + "_"
675: + className;
676: }
677:
678: /**
679: * Dump lock table, for debugging
680: */
681: public void dumpLockTable() throws DatabaseException {
682:
683: lockManager.dump();
684: }
685: }
|