KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > je > txn > Txn


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: Txn.java,v 1.147 2006/11/17 23:47:28 mark Exp $
7  */

8
9 package com.sleepycat.je.txn;
10
11 import java.nio.ByteBuffer JavaDoc;
12 import java.util.ArrayList JavaDoc;
13 import java.util.HashMap JavaDoc;
14 import java.util.HashSet JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18 import java.util.Set JavaDoc;
19 import java.util.logging.Level JavaDoc;
20 import java.util.logging.Logger JavaDoc;
21
22 import javax.transaction.xa.XAResource JavaDoc;
23 import javax.transaction.xa.Xid JavaDoc;
24
25 import com.sleepycat.je.Database;
26 import com.sleepycat.je.DatabaseException;
27 import com.sleepycat.je.DbInternal;
28 import com.sleepycat.je.LockStats;
29 import com.sleepycat.je.RunRecoveryException;
30 import com.sleepycat.je.TransactionConfig;
31 import com.sleepycat.je.dbi.CursorImpl;
32 import com.sleepycat.je.dbi.DatabaseId;
33 import com.sleepycat.je.dbi.DatabaseImpl;
34 import com.sleepycat.je.dbi.EnvironmentImpl;
35 import com.sleepycat.je.dbi.MemoryBudget;
36 import com.sleepycat.je.log.LogManager;
37 import com.sleepycat.je.log.LogReadable;
38 import com.sleepycat.je.log.LogUtils;
39 import com.sleepycat.je.log.LogWritable;
40 import com.sleepycat.je.log.entry.LNLogEntry;
41 import com.sleepycat.je.recovery.RecoveryManager;
42 import com.sleepycat.je.tree.LN;
43 import com.sleepycat.je.tree.TreeLocation;
44 import com.sleepycat.je.utilint.DbLsn;
45 import com.sleepycat.je.utilint.Tracer;
46
47 /**
48  * A Txn is one that's created by a call to Environment.txnBegin. This class
49  * must support multithreaded use.
50  */

51 public class Txn extends Locker implements LogWritable, LogReadable {
52     public static final byte TXN_NOSYNC = 0;
53     public static final byte TXN_WRITE_NOSYNC = 1;
54     public static final byte TXN_SYNC = 2;
55
56     /**
57      * Static log size used by LNLogEntry.getStaticLogSize.
58      */

59     public static int LOG_SIZE = LogUtils.LONG_BYTES + // id
60
LogUtils.LONG_BYTES; // lastLoggedLsn
61

62     private static final String JavaDoc DEBUG_NAME =
63         Txn.class.getName();
64
65     private byte txnState;
66
67     /*
68      * Cursors opened under this txn. Implemented as a simple linked list to
69      * conserve on memory.
70      */

71     private CursorImpl cursorSet;
72
73     /* txnState bits. */
74     private static final byte USABLE = 0;
75     private static final byte CLOSED = 1;
76     private static final byte ONLY_ABORTABLE = 2;
77     private static final byte STATE_BITS = 3;
78     /* Set if prepare() has been called on this transaction. */
79     private static final byte IS_PREPARED = 4;
80     /* Set if xa_end(TMSUSPEND) has been called on this transaction. */
81     private static final byte XA_SUSPENDED = 8;
82
83     /*
84      * A Txn can be used by multiple threads. Modification to the read and
85      * write lock collections is done by synchronizing on the txn.
86      */

87     private Set JavaDoc readLocks;
88     private Map JavaDoc writeInfo; // key=nodeid, data = WriteLockInfo
89

90     private final int READ_LOCK_OVERHEAD = MemoryBudget.HASHSET_ENTRY_OVERHEAD;
91     private final int WRITE_LOCK_OVERHEAD =
92     MemoryBudget.HASHMAP_ENTRY_OVERHEAD +
93     MemoryBudget.WRITE_LOCKINFO_OVERHEAD;
94
95     /*
96      * We have to keep a set of DatabaseCleanupInfo objects so after
97      * commit or abort of Environment.truncateDatabase() or
98      * Environment.removeDatabase(), we can appropriately purge the
99      * unneeded MapLN and DatabaseImpl.
100      * Synchronize access to this set on this object.
101      */

102     private Set JavaDoc deletedDatabases;
103
104     /*
105      * We need a map of the latest databaseImpl objects to drive the undo
106      * during an abort, because it's too hard to look up the database object in
107      * the mapping tree. (The normal code paths want to take locks, add
108      * cursors, etc.
109      */

110     private Map JavaDoc undoDatabases;
111
112     /* Last LSN logged for this transaction. */
113     private long lastLoggedLsn = DbLsn.NULL_LSN;
114
115     /*
116      * First LSN logged for this transaction -- used for keeping track of the
117      * first active LSN point, for checkpointing. This field is not persistent.
118      */

119     private long firstLoggedLsn = DbLsn.NULL_LSN;
120
121     /* Whether to flush and sync on commit by default. */
122     private byte defaultFlushSyncBehavior;
123
124     /* Whether to use Serializable isolation (prevent phantoms). */
125     private boolean serializableIsolation;
126
127     /* Whether to use Read-Committed isolation. */
128     private boolean readCommittedIsolation;
129
130     /*
131      * In-memory size, in bytes. A Txn tracks the memory needed for itself and
132      * the readlock, writeInfo, undoDatabases, and deletedDatabases
133      * collections, including the cost of each collection entry. However, the
134      * actual Lock object memory cost is maintained within the Lock class.
135      */

136     private int inMemorySize;
137     
138     /*
139      * accumluted memory budget delta. Once this exceeds
140      * ACCUMULATED_LIMIT we inform the MemoryBudget that a change
141      * has occurred.
142      */

143     private int accumulatedDelta = 0;
144
145     /*
146      * Max allowable accumulation of memory budget changes before MemoryBudget
147      * should be updated. This allows for consolidating multiple calls to
148      * updateXXXMemoryBudget() into one call. Not declared final so that unit
149      * tests can modify this. See SR 12273.
150      */

151     public static int ACCUMULATED_LIMIT = 10000;
152
153     /**
154      * Create a transaction from Environment.txnBegin.
155      */

156     public Txn(EnvironmentImpl envImpl, TransactionConfig config)
157         throws DatabaseException {
158
159         /*
160          * Initialize using the config but don't hold a reference to it, since
161          * it has not been cloned.
162          */

163         super(envImpl, config.getReadUncommitted(), config.getNoWait());
164     init(envImpl, config);
165     }
166
167     public Txn(EnvironmentImpl envImpl, TransactionConfig config, long id)
168         throws DatabaseException {
169
170         /*
171          * Initialize using the config but don't hold a reference to it, since
172          * it has not been cloned.
173          */

174         super(envImpl, config.getReadUncommitted(), config.getNoWait());
175     init(envImpl, config);
176
177     this.id = id;
178     }
179
180     private void init(EnvironmentImpl envImpl, TransactionConfig config)
181     throws DatabaseException {
182
183         serializableIsolation = config.getSerializableIsolation();
184         readCommittedIsolation = config.getReadCommitted();
185
186         /*
187          * Figure out what we should do on commit. TransactionConfig could be
188          * set with conflicting values; take the most stringent ones first.
189          * All environment level defaults were applied by the caller.
190          *
191          * ConfigSync ConfigWriteNoSync ConfigNoSync default
192          * 0 0 0 sync
193          * 0 0 1 nosync
194          * 0 1 0 write nosync
195          * 0 1 1 write nosync
196          * 1 0 0 sync
197          * 1 0 1 sync
198          * 1 1 0 sync
199          * 1 1 1 sync
200          */

201         if (config.getSync()) {
202             defaultFlushSyncBehavior = TXN_SYNC;
203         } else if (config.getWriteNoSync()) {
204             defaultFlushSyncBehavior = TXN_WRITE_NOSYNC;
205         } else if (config.getNoSync()) {
206             defaultFlushSyncBehavior = TXN_NOSYNC;
207         } else {
208             defaultFlushSyncBehavior = TXN_SYNC;
209         }
210
211         lastLoggedLsn = DbLsn.NULL_LSN;
212         firstLoggedLsn = DbLsn.NULL_LSN;
213
214         txnState = USABLE;
215
216         /*
217          * Note: readLocks, writeInfo, undoDatabases, deleteDatabases are
218          * initialized lazily in order to conserve memory. WriteInfo and
219          * undoDatabases are treated as a package deal, because they are both
220          * only needed if a transaction does writes.
221          *
222          * When a lock is added to this transaction, we add the collection
223          * entry overhead to the memory cost, but don't add the lock
224          * itself. That's taken care of by the Lock class.
225          */

226     updateMemoryUsage(MemoryBudget.TXN_OVERHEAD);
227
228         this.envImpl.getTxnManager().registerTxn(this);
229     }
230
231     /**
232      * Constructor for reading from log.
233      */

234     public Txn() {
235         lastLoggedLsn = DbLsn.NULL_LSN;
236     }
237
238     /**
239      * UserTxns get a new unique id for each instance.
240      */

241     protected long generateId(TxnManager txnManager) {
242         return txnManager.incTxnId();
243     }
244
245     /**
246      * Access to last LSN.
247      */

248     long getLastLsn() {
249         return lastLoggedLsn;
250     }
251
252     public void setPrepared(boolean prepared) {
253     if (prepared) {
254         txnState |= IS_PREPARED;
255     } else {
256         txnState &= ~IS_PREPARED;
257     }
258     }
259
260     public void setSuspended(boolean suspended) {
261     if (suspended) {
262         txnState |= XA_SUSPENDED;
263     } else {
264         txnState &= ~XA_SUSPENDED;
265     }
266     }
267
268     public boolean isSuspended() {
269     return (txnState & XA_SUSPENDED) != 0;
270     }
271
272     /**
273      * Gets a lock on this nodeId and, if it is a write lock, saves an abort
274      * LSN. Caller will set the abortLsn later, after the write lock has been
275      * obtained.
276      *
277      * @see Locker#lockInternal
278      * @Override
279      */

280     LockResult lockInternal(long nodeId,
281                 LockType lockType,
282                             boolean noWait,
283                             DatabaseImpl database)
284         throws DatabaseException {
285
286     long timeout = 0;
287         boolean useNoWait = noWait || defaultNoWait;
288     synchronized (this) {
289         checkState(false);
290             if (!useNoWait) {
291                 timeout = lockTimeOutMillis;
292             }
293     }
294
295         /* Ask for the lock. */
296         LockGrantType grant = lockManager.lock
297             (nodeId, this, lockType, timeout, useNoWait, database);
298     
299     WriteLockInfo info = null;
300     if (writeInfo != null) {
301         if (grant != LockGrantType.DENIED && lockType.isWriteLock()) {
302         synchronized (this) {
303             info = (WriteLockInfo) writeInfo.get(new Long JavaDoc(nodeId));
304             /* Save the latest version of this database for undoing. */
305             undoDatabases.put(database.getId(), database);
306         }
307         }
308     }
309
310     return new LockResult(grant, info);
311     }
312
313     public int prepare(Xid JavaDoc xid)
314     throws DatabaseException {
315
316     if ((txnState & IS_PREPARED) != 0) {
317         throw new DatabaseException
318         ("prepare() has already been called for Transaction " +
319          id + ".");
320     }
321     synchronized (this) {
322         checkState(false);
323         if (checkCursorsForClose()) {
324         throw new DatabaseException
325             ("Transaction " + id +
326              " prepare failed because there were open cursors.");
327         }
328
329         TxnPrepare prepareRecord =
330         new TxnPrepare(id, xid); /* Flush required. */
331         LogManager logManager = envImpl.getLogManager();
332         logManager.logForceFlush(prepareRecord, true); // sync required
333
}
334     setPrepared(true);
335     return XAResource.XA_OK;
336     }
337
338     public void commit(Xid JavaDoc xid)
339     throws DatabaseException {
340
341     commit(TXN_SYNC);
342         envImpl.getTxnManager().unRegisterXATxn(xid, true);
343     return;
344     }
345
346     public void abort(Xid JavaDoc xid)
347     throws DatabaseException {
348
349     abort(true);
350         envImpl.getTxnManager().unRegisterXATxn(xid, false);
351     return;
352     }
353
354     /**
355      * Call commit() with the default sync configuration property.
356      */

357     public long commit()
358         throws DatabaseException {
359
360         return commit(defaultFlushSyncBehavior);
361     }
362
363     /**
364      * Commit this transaction
365      * 1. Releases read locks
366      * 2. Writes a txn commit record into the log
367      * 3. Flushes the log to disk.
368      * 4. Add deleted LN info to IN compressor queue
369      * 5. Release all write locks
370      *
371      * If any step of this fails, we must convert this transaction to an abort.
372      */

373     public long commit(byte flushSyncBehavior)
374         throws DatabaseException {
375
376         try {
377             long commitLsn = DbLsn.NULL_LSN;
378             synchronized (this) {
379         checkState(false);
380                 if (checkCursorsForClose()) {
381                     throw new DatabaseException
382                         ("Transaction " + id +
383                          " commit failed because there were open cursors.");
384                 }
385
386                 /*
387                  * Save transferred write locks, if any. Their abort LSNs are
388                  * counted as obsolete further below. Create the list lazily
389                  * to avoid creating it in the normal case (no handle locks).
390                  */

391                 List JavaDoc transferredWriteLockInfo = null;
392
393                 /* Transfer handle locks to their owning handles. */
394                 if (handleLockToHandleMap != null) {
395                     Iterator JavaDoc handleLockIter =
396                         handleLockToHandleMap.entrySet().iterator();
397                     while (handleLockIter.hasNext()){
398                         Map.Entry JavaDoc entry = (Map.Entry JavaDoc) handleLockIter.next();
399                         Long JavaDoc nodeId = (Long JavaDoc) entry.getKey();
400                         if (writeInfo != null) {
401                             WriteLockInfo info =
402                                 (WriteLockInfo) writeInfo.get(nodeId);
403                             if (info != null) {
404                                 if (transferredWriteLockInfo == null) {
405                                     transferredWriteLockInfo = new ArrayList JavaDoc();
406                                 }
407                                 transferredWriteLockInfo.add(info);
408                             }
409                         }
410                         transferHandleLockToHandleSet
411                             (nodeId, (Set JavaDoc) entry.getValue());
412                     }
413                 }
414
415                 LogManager logManager = envImpl.getLogManager();
416
417                 /*
418                  * Release all read locks, clear lock collection. Optimize for
419                  * the case where there are no read locks.
420                  */

421                 int numReadLocks = clearReadLocks();
422
423                 /*
424                  * Log the commit if we ever held any write locks. Note that
425                  * with dbhandle write locks, we may have held the write lock
426                  * but then had it transferred away.
427                  */

428                 int numWriteLocks = 0;
429                 if (writeInfo != null) {
430                     numWriteLocks = writeInfo.size();
431                     TxnCommit commitRecord =
432                         new TxnCommit(id, lastLoggedLsn);
433             if (flushSyncBehavior == TXN_SYNC) {
434             /* Flush and sync required. */
435             commitLsn = logManager.
436                 logForceFlush(commitRecord, true);
437             } else if (flushSyncBehavior == TXN_WRITE_NOSYNC) {
438             /* Flush but no sync required. */
439             commitLsn = logManager.
440                 logForceFlush(commitRecord, false);
441             } else {
442             /* No flush, no sync required. */
443             commitLsn = logManager.log(commitRecord);
444             }
445                 
446                     /*
447                      * Set database state for deletes before releasing any
448                      * write locks.
449                      */

450                     setDeletedDatabaseState(true);
451
452                     /*
453                      * Used to prevent double counting abortLNS if there is
454                      * more then one node with the same abortLSN in this txn.
455                      * Two nodes with the same abortLSN occur when a deleted
456                      * slot is reused in the same txn.
457                      */

458                     Set JavaDoc alreadyCountedLsnSet = new HashSet JavaDoc();
459
460                     /* Release all write locks, clear lock collection. */
461                     Iterator JavaDoc iter = writeInfo.values().iterator();
462                     while (iter.hasNext()) {
463                         WriteLockInfo info = (WriteLockInfo) iter.next();
464                         lockManager.release(info.lock, this);
465                         /* Count obsolete LSNs for released write locks. */
466                         countWriteAbortLSN(info, alreadyCountedLsnSet);
467                     }
468                     writeInfo = null;
469
470                     /* Count obsolete LSNs for transferred write locks. */
471                     if (transferredWriteLockInfo != null) {
472                         for (int i = 0;
473                              i < transferredWriteLockInfo.size();
474                              i += 1) {
475                             WriteLockInfo info = (WriteLockInfo)
476                                 transferredWriteLockInfo.get(i);
477                             countWriteAbortLSN(info, alreadyCountedLsnSet);
478                         }
479                     }
480
481                     /* Unload delete info, but don't wake up the compressor. */
482                     if ((deleteInfo != null) && deleteInfo.size() > 0) {
483                         envImpl.addToCompressorQueue(deleteInfo.values(),
484                                                      false); // don't wakeup
485
deleteInfo.clear();
486                     }
487                 }
488
489                 traceCommit(numWriteLocks, numReadLocks);
490             }
491
492             /*
493              * Purge any databaseImpls not needed as a result of the commit.
494              * Be sure to do this outside the synchronization block, to avoid
495              * conflict w/checkpointer.
496              */

497             cleanupDatabaseImpls(true);
498
499             /*
500              * Unregister this txn. Be sure to do this outside the
501              * synchronization block, to avoid conflict w/checkpointer.
502              */

503             close(true);
504             return commitLsn;
505         } catch (RunRecoveryException e) {
506
507             /* May have received a thread interrupt. */
508             throw e;
509         } catch (Throwable JavaDoc t) {
510
511             try {
512
513         /*
514          * If the exception thrown is a DatabaseException it indicates
515          * that the write() call hit an IOException, probably out of
516          * disk space, and attempted to rewrite all commit records as
517          * abort records. Since the abort records are already
518          * rewritten (or at least attempted to be rewritten), there is
519          * no reason to have abort attempt to write an abort record
520          * again. See [11271].
521          */

522                 abortInternal(flushSyncBehavior == TXN_SYNC,
523                   !(t instanceof DatabaseException));
524                 Tracer.trace(envImpl, "Txn", "commit",
525                              "Commit of transaction " + id + " failed", t);
526             } catch (Throwable JavaDoc abortT2) {
527                 throw new DatabaseException
528                     ("Failed while attempting to commit transaction " +
529                      id +
530                      ". The attempt to abort and clean up also failed. " +
531                      "The original exception seen from commit = " +
532                      t.getMessage() +
533                      " The exception from the cleanup = " +
534                      abortT2.getMessage(),
535                      t);
536             }
537                 
538             /* Now throw an exception that shows the commit problem. */
539             throw new DatabaseException
540                 ("Failed while attempting to commit transaction " + id +
541                  ", aborted instead. Original exception = " +
542                  t.getMessage(), t);
543         }
544     }
545
546     /**
547      * Count the abortLSN as obsolete. Do not count if a slot with a deleted
548      * LN was reused (abortKnownDeleted), to avoid double counting. And count
549      * each abortLSN only once.
550      */

551     private void countWriteAbortLSN(WriteLockInfo info,
552                                     Set JavaDoc alreadyCountedLsnSet)
553         throws DatabaseException {
554
555         if (info.abortLsn != DbLsn.NULL_LSN &&
556             !info.abortKnownDeleted) {
557             Long JavaDoc longLsn = new Long JavaDoc(info.abortLsn);
558             if (!alreadyCountedLsnSet.contains(longLsn)) {
559                 envImpl.getLogManager().countObsoleteNode
560                     (info.abortLsn, null, info.abortLogSize);
561                 alreadyCountedLsnSet.add(longLsn);
562             }
563         }
564     }
565
566     /**
567      * Abort this transaction. Steps are:
568      * 1. Release LN read locks.
569      * 2. Write a txn abort entry to the log. This is only for log
570      * file cleaning optimization and there's no need to guarantee a
571      * flush to disk.
572      * 3. Find the last LN log entry written for this txn, and use that
573      * to traverse the log looking for nodes to undo. For each node,
574      * use the same undo logic as recovery to rollback the transaction. Note
575      * that we walk the log in order to undo in reverse order of the
576      * actual operations. For example, suppose the txn did this:
577      * delete K1/D1 (in LN 10)
578      * create K1/D1 (in LN 20)
579      * If we process LN10 before LN 20, we'd inadvertently create a
580      * duplicate tree of "K1", which would be fatal for the mapping tree.
581      * 4. Release the write lock for this LN.
582      */

583     public long abort(boolean forceFlush)
584         throws DatabaseException {
585
586     return abortInternal(forceFlush, true);
587     }
588
589     private long abortInternal(boolean forceFlush, boolean writeAbortRecord)
590     throws DatabaseException {
591
592         try {
593             int numReadLocks;
594             int numWriteLocks;
595             long abortLsn;
596
597             synchronized (this) {
598                 checkState(true);
599
600                 /* Log the abort. */
601                 TxnAbort abortRecord = new TxnAbort(id, lastLoggedLsn);
602                 abortLsn = DbLsn.NULL_LSN;
603                 if (writeInfo != null) {
604             if (writeAbortRecord) {
605             if (forceFlush) {
606                 abortLsn = envImpl.getLogManager().
607                 logForceFlush(abortRecord, true);
608             } else {
609                 abortLsn =
610                 envImpl.getLogManager().log(abortRecord);
611             }
612             }
613                 }
614
615                 /* Undo the changes. */
616                 undo();
617
618                 /*
619                  * Release all read locks after the undo (since the undo may
620                  * need to read in mapLNs).
621                  */

622                 numReadLocks = (readLocks == null) ? 0 : clearReadLocks();
623
624                 /*
625                  * Set database state for deletes before releasing any write
626                  * locks.
627                  */

628                 setDeletedDatabaseState(false);
629
630                 /* Throw away write lock collection. */
631                 numWriteLocks = (writeInfo == null) ? 0 : clearWriteLocks();
632
633                 /*
634                  * Let the delete related info (binreferences and dbs) get
635                  * gc'ed. Don't explicitly iterate and clear -- that's far less
636                  * efficient, gives GC wrong input.
637                  */

638                 deleteInfo = null;
639             }
640
641             /*
642              * Purge any databaseImpls not needed as a result of the abort. Be
643              * sure to do this outside the synchronization block, to avoid
644              * conflict w/checkpointer.
645              */

646             cleanupDatabaseImpls(false);
647
648             synchronized (this) {
649                 boolean openCursors = checkCursorsForClose();
650                 Tracer.trace(Level.FINE,
651                              envImpl,
652                              "Abort:id = " + id +
653                              " numWriteLocks= " + numWriteLocks +
654                              " numReadLocks= " + numReadLocks +
655                              " openCursors= " + openCursors);
656                 if (openCursors) {
657                     throw new DatabaseException
658                         ("Transaction " + id +
659                          " detected open cursors while aborting");
660                 }
661                 /* Unload any db handles protected by this txn. */
662                 if (handleToHandleLockMap != null) {
663                     Iterator JavaDoc handleIter =
664                         handleToHandleLockMap.keySet().iterator();
665                     while (handleIter.hasNext()){
666                         Database handle = (Database) handleIter.next();
667                         DbInternal.dbInvalidate(handle);
668                     }
669                 }
670
671                 return abortLsn;
672             }
673         } finally {
674
675             /*
676              * Unregister this txn, must be done outside synchronization block
677              * to avoid conflict w/checkpointer.
678              */

679             close(false);
680         }
681     }
682
683     /**
684      * Rollback the changes to this txn's write locked nodes.
685      */

686     private void undo()
687         throws DatabaseException {
688         
689         Long JavaDoc nodeId = null;
690         long undoLsn = lastLoggedLsn;
691         LogManager logManager = envImpl.getLogManager();
692
693         try {
694             Set JavaDoc alreadyUndone = new HashSet JavaDoc();
695             TreeLocation location = new TreeLocation();
696             while (undoLsn != DbLsn.NULL_LSN) {
697
698                 LNLogEntry undoEntry =
699             (LNLogEntry) logManager.getLogEntry(undoLsn);
700                 LN undoLN = undoEntry.getLN();
701                 nodeId = new Long JavaDoc(undoLN.getNodeId());
702
703                 /*
704                  * Only process this if this is the first time we've seen this
705                  * node. All log entries for a given node have the same
706                  * abortLsn, so we don't need to undo it multiple times.
707                  */

708                 if (!alreadyUndone.contains(nodeId)) {
709                     alreadyUndone.add(nodeId);
710                     DatabaseId dbId = undoEntry.getDbId();
711                     DatabaseImpl db = (DatabaseImpl) undoDatabases.get(dbId);
712                     undoLN.postFetchInit(db, undoLsn);
713                     long abortLsn = undoEntry.getAbortLsn();
714                     boolean abortKnownDeleted =
715                         undoEntry.getAbortKnownDeleted();
716                     try {
717                         RecoveryManager.undo(Level.FINER,
718                                              db,
719                                              location,
720                                              undoLN,
721                                              undoEntry.getKey(),
722                                              undoEntry.getDupKey(),
723                                              undoLsn,
724                                              abortLsn,
725                                              abortKnownDeleted,
726                                              null, false);
727                     } finally {
728                         if (location.bin != null) {
729                             location.bin.releaseLatchIfOwner();
730                         }
731                     }
732             
733                     /*
734                      * The LN undone is counted as obsolete if it is not a
735                      * deleted LN. Deleted LNs are counted as obsolete when
736                      * they are logged.
737                      */

738                     if (!undoLN.isDeleted()) {
739                         logManager.countObsoleteNode
740                             (undoLsn, null,
741                              undoEntry.getLogSize() + LogManager.HEADER_BYTES);
742                     }
743                 }
744
745                 /* Move on to the previous log entry for this txn. */
746                 undoLsn = undoEntry.getUserTxn().getLastLsn();
747             }
748         } catch (RuntimeException JavaDoc e) {
749             throw new DatabaseException("Txn undo for node=" + nodeId +
750                                         " LSN=" +
751                                         DbLsn.getNoFormatString(undoLsn), e);
752         } catch (DatabaseException e) {
753             Tracer.trace(envImpl, "Txn", "undo",
754              "for node=" + nodeId + " LSN=" +
755              DbLsn.getNoFormatString(undoLsn), e);
756             throw e;
757         }
758     }
759
760     private int clearWriteLocks()
761     throws DatabaseException {
762
763     int numWriteLocks = writeInfo.size();
764     /* Release all write locks, clear lock collection. */
765     Iterator JavaDoc iter = writeInfo.values().iterator();
766     while (iter.hasNext()) {
767         WriteLockInfo info = (WriteLockInfo) iter.next();
768         lockManager.release(info.lock, this);
769     }
770     writeInfo = null;
771     return numWriteLocks;
772     }
773
774     private int clearReadLocks()
775     throws DatabaseException {
776
777     int numReadLocks = 0;
778     if (readLocks != null) {
779         numReadLocks = readLocks.size();
780         Iterator JavaDoc iter = readLocks.iterator();
781         while (iter.hasNext()) {
782         Lock rLock = (Lock) iter.next();
783         lockManager.release(rLock, this);
784         }
785         readLocks = null;
786     }
787     return numReadLocks;
788     }
789
790     /**
791      * Called by the recovery manager when logging a transaction aware object.
792      * This method is synchronized by the caller, by being called within the
793      * log latch. Record the last LSN for this transaction, to create the
794      * transaction chain, and also record the LSN in the write info for abort
795      * logic.
796      */

797     public void addLogInfo(long lastLsn)
798         throws DatabaseException {
799
800         /* Save the last LSN for maintaining the transaction LSN chain. */
801         lastLoggedLsn = lastLsn;
802
803         /* Save handle to LSN for aborts. */
804         synchronized (this) {
805
806             /*
807              * If this is the first LSN, save it for calculating the first LSN
808              * of any active txn, for checkpointing.
809              */

810             if (firstLoggedLsn == DbLsn.NULL_LSN) {
811                 firstLoggedLsn = lastLsn;
812             }
813         }
814     }
815
816     /**
817      * @return first logged LSN, to aid recovery rollback.
818      */

819     long getFirstActiveLsn()
820         throws DatabaseException {
821
822         synchronized (this) {
823             return firstLoggedLsn;
824         }
825     }
826
827     /**
828      * @param dbImpl databaseImpl to remove
829      * @param deleteAtCommit true if this databaseImpl should be cleaned on
830      * commit, false if it should be cleaned on abort.
831      * @param mb environment memory budget.
832      */

833     public void markDeleteAtTxnEnd(DatabaseImpl dbImpl, boolean deleteAtCommit)
834         throws DatabaseException {
835
836         synchronized (this) {
837             int delta = 0;
838             if (deletedDatabases == null) {
839                 deletedDatabases = new HashSet JavaDoc();
840                 delta += MemoryBudget.HASHSET_OVERHEAD;
841             }
842
843             deletedDatabases.add(new DatabaseCleanupInfo(dbImpl,
844                                                          deleteAtCommit));
845             delta += MemoryBudget.HASHSET_ENTRY_OVERHEAD +
846                 MemoryBudget.OBJECT_OVERHEAD;
847         updateMemoryUsage(delta);
848         }
849     }
850
851     /*
852      * Leftover databaseImpls that are a by-product of database operations like
853      * removeDatabase(), truncateDatabase() will be deleted after the write
854      * locks are released. However, do set the database state appropriately
855      * before the locks are released.
856      */

857     private void setDeletedDatabaseState(boolean isCommit)
858         throws DatabaseException {
859
860         if (deletedDatabases != null) {
861             Iterator JavaDoc iter = deletedDatabases.iterator();
862             while (iter.hasNext()) {
863                 DatabaseCleanupInfo info = (DatabaseCleanupInfo) iter.next();
864                 if (info.deleteAtCommit == isCommit) {
865                     info.dbImpl.startDeleteProcessing();
866                 }
867             }
868         }
869     }
870
871     /**
872      * Cleanup leftover databaseImpls that are a by-product of database
873      * operations like removeDatabase(), truncateDatabase().
874      *
875      * This method must be called outside the synchronization on this txn,
876      * because it calls deleteAndReleaseINs, which gets the TxnManager's
877      * allTxns latch. The checkpointer also gets the allTxns latch, and within
878      * that latch, needs to synchronize on individual txns, so we must avoid a
879      * latching hiearchy conflict.
880      */

881     private void cleanupDatabaseImpls(boolean isCommit)
882         throws DatabaseException {
883
884         if (deletedDatabases != null) {
885             /* Make a copy of the deleted databases while synchronized. */
886             DatabaseCleanupInfo[] infoArray;
887             synchronized (this) {
888                 infoArray = new DatabaseCleanupInfo[deletedDatabases.size()];
889                 deletedDatabases.toArray(infoArray);
890             }
891             for (int i = 0; i < infoArray.length; i += 1) {
892                 DatabaseCleanupInfo info = infoArray[i];
893                 if (info.deleteAtCommit == isCommit) {
894                     info.dbImpl.releaseDeletedINs();
895                 }
896             }
897             deletedDatabases = null;
898         }
899     }
900
901     /**
902      * Add lock to the appropriate queue.
903      */

904     void addLock(Long JavaDoc nodeId,
905          Lock lock,
906                  LockType type,
907          LockGrantType grantStatus)
908         throws DatabaseException {
909
910         synchronized (this) {
911             int delta = 0;
912             if (type.isWriteLock()) {
913                 if (writeInfo == null) {
914                     writeInfo = new HashMap JavaDoc();
915                     undoDatabases = new HashMap JavaDoc();
916                     delta += MemoryBudget.TWOHASHMAPS_OVERHEAD;
917                 }
918
919                 writeInfo.put(nodeId,
920                               new WriteLockInfo(lock));
921                 delta += WRITE_LOCK_OVERHEAD;
922                 
923                 if ((grantStatus == LockGrantType.PROMOTION) ||
924                     (grantStatus == LockGrantType.WAIT_PROMOTION)) {
925                     readLocks.remove(lock);
926                     delta -= READ_LOCK_OVERHEAD;
927                 }
928         updateMemoryUsage(delta);
929             } else {
930                 addReadLock(lock);
931             }
932         }
933     }
934
935     private void addReadLock(Lock lock) {
936         int delta = 0;
937         if (readLocks == null) {
938             readLocks = new HashSet JavaDoc();
939             delta = MemoryBudget.HASHSET_OVERHEAD;
940         }
941         
942         readLocks.add(lock);
943         delta += READ_LOCK_OVERHEAD;
944     updateMemoryUsage(delta);
945     }
946
947     /**
948      * Remove the lock from the set owned by this transaction. If specified to
949      * LockManager.release, the lock manager will call this when its releasing
950      * a lock. Usually done because the transaction doesn't need to really keep
951      * the lock, i.e for a deleted record.
952      */

953     void removeLock(long nodeId, Lock lock)
954         throws DatabaseException {
955
956         /*
957          * We could optimize by passing the lock type so we know which
958          * collection to look in. Be careful of demoted locks, which have
959          * shifted collection.
960          *
961          * Don't bother updating memory utilization here -- we'll update at
962          * transaction end.
963          */

964         synchronized (this) {
965         if ((readLocks != null) &&
966         readLocks.remove(lock)) {
967         updateMemoryUsage(0 - READ_LOCK_OVERHEAD);
968         } else if ((writeInfo != null) &&
969                (writeInfo.remove(new Long JavaDoc(nodeId)) != null)) {
970         updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD);
971             }
972         }
973     }
974
975     /**
976      * A lock is being demoted. Move it from the write collection into the read
977      * collection.
978      */

979     void moveWriteToReadLock(long nodeId, Lock lock) {
980         
981         boolean found = false;
982         synchronized (this) {
983             if ((writeInfo != null) &&
984                 (writeInfo.remove(new Long JavaDoc(nodeId)) != null)) {
985                 found = true;
986         updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD);
987             }
988
989             assert found : "Couldn't find lock for Node " + nodeId +
990                 " in writeInfo Map.";
991             addReadLock(lock);
992         }
993     }
994
995     private void updateMemoryUsage(int delta) {
996         inMemorySize += delta;
997     accumulatedDelta += delta;
998     if (accumulatedDelta > ACCUMULATED_LIMIT ||
999         accumulatedDelta < -ACCUMULATED_LIMIT) {
1000        envImpl.getMemoryBudget().updateMiscMemoryUsage(accumulatedDelta);
1001        accumulatedDelta = 0;
1002    }
1003    }
1004
1005    int getAccumulatedDelta() {
1006    return accumulatedDelta;
1007    }
1008
1009    /**
1010     * @return true if this transaction created this node. We know that this
1011     * is true if the node is write locked and has a null abort LSN.
1012     */

1013    public boolean createdNode(long nodeId)
1014        throws DatabaseException {
1015
1016        boolean created = false;
1017        synchronized (this) {
1018            if (writeInfo != null) {
1019                WriteLockInfo info = (WriteLockInfo)
1020                    writeInfo.get(new Long JavaDoc(nodeId));
1021                if (info != null) {
1022                    created = info.createdThisTxn;
1023                }
1024            }
1025    }
1026        return created;
1027    }
1028
1029    /**
1030     * @return the abortLsn for this node.
1031     */

1032    public long getAbortLsn(long nodeId)
1033        throws DatabaseException {
1034
1035        WriteLockInfo info = null;
1036        synchronized (this) {
1037            if (writeInfo != null) {
1038                info = (WriteLockInfo) writeInfo.get(new Long JavaDoc(nodeId));
1039            }
1040        }
1041
1042        if (info == null) {
1043            return DbLsn.NULL_LSN;
1044        } else {
1045            return info.abortLsn;
1046        }
1047    }
1048
1049    /**
1050     * @return the WriteLockInfo for this node.
1051     */

1052    public WriteLockInfo getWriteLockInfo(long nodeId)
1053    throws DatabaseException {
1054
1055        WriteLockInfo info = WriteLockInfo.basicWriteLockInfo;
1056        synchronized (this) {
1057            if (writeInfo != null) {
1058                info = (WriteLockInfo) writeInfo.get(new Long JavaDoc(nodeId));
1059            }
1060        }
1061
1062    return info;
1063    }
1064
1065    /**
1066     * Is always transactional.
1067     */

1068    public boolean isTransactional() {
1069        return true;
1070    }
1071
1072    /**
1073     * Is serializable isolation if so configured.
1074     */

1075    public boolean isSerializableIsolation() {
1076        return serializableIsolation;
1077    }
1078
1079    /**
1080     * Is read-committed isolation if so configured.
1081     */

1082    public boolean isReadCommittedIsolation() {
1083        return readCommittedIsolation;
1084    }
1085
1086    /**
1087     * This is a transactional locker.
1088     */

1089    public Txn getTxnLocker() {
1090        return this;
1091    }
1092
1093    /**
1094     * Returns 'this', since this locker holds no non-transactional locks.
1095     */

1096    public Locker newNonTxnLocker()
1097        throws DatabaseException {
1098
1099        return this;
1100    }
1101
1102    /**
1103     * This locker holds no non-transactional locks.
1104     */

1105    public void releaseNonTxnLocks()
1106        throws DatabaseException {
1107    }
1108
1109    /**
1110     * Created transactions do nothing at the end of the operation.
1111     */

1112    public void operationEnd()
1113        throws DatabaseException {
1114    }
1115
1116    /**
1117     * Created transactions do nothing at the end of the operation.
1118     */

1119    public void operationEnd(boolean operationOK)
1120        throws DatabaseException {
1121    }
1122
1123    /**
1124     * Created transactions don't transfer locks until commit.
1125     */

1126    public void setHandleLockOwner(boolean ignore /*operationOK*/,
1127                                   Database dbHandle,
1128                                   boolean dbIsClosing)
1129        throws DatabaseException {
1130
1131        if (dbIsClosing) {
1132
1133            /*
1134             * If the Database handle is closing, take it out of the both the
1135             * handle lock map and the handle map. We don't need to do any
1136             * transfers at commit time, and we don't need to do any
1137             * invalidations at abort time.
1138             */

1139            Long JavaDoc handleLockId = (Long JavaDoc) handleToHandleLockMap.get(dbHandle);
1140            if (handleLockId != null) {
1141                Set JavaDoc dbHandleSet = (Set JavaDoc)
1142            handleLockToHandleMap.get(handleLockId);
1143                boolean removed = dbHandleSet.remove(dbHandle);
1144                assert removed :
1145            "Can't find " + dbHandle + " from dbHandleSet";
1146                if (dbHandleSet.size() == 0) {
1147                    Object JavaDoc foo = handleLockToHandleMap.remove(handleLockId);
1148                    assert (foo != null) :
1149            "Can't find " + handleLockId +
1150            " from handleLockIdtoHandleMap.";
1151                }
1152            }
1153
1154            unregisterHandle(dbHandle);
1155
1156        } else {
1157
1158            /*
1159             * If the db is still open, make sure the db knows this txn is its
1160             * handle lock protector and that this txn knows it owns this db
1161             * handle.
1162             */

1163            if (dbHandle != null) {
1164                DbInternal.dbSetHandleLocker(dbHandle, this);
1165            }
1166        }
1167    }
1168
1169    /**
1170     * Cursors operating under this transaction are added to the collection.
1171     */

1172    public void registerCursor(CursorImpl cursor)
1173        throws DatabaseException {
1174
1175        synchronized(this) {
1176            /* Add to the head of the list. */
1177            cursor.setLockerNext(cursorSet);
1178            if (cursorSet != null) {
1179                cursorSet.setLockerPrev(cursor);
1180            }
1181            cursorSet = cursor;
1182        }
1183    }
1184
1185    /**
1186     * Remove a cursor from the collection.
1187     */

1188    public void unRegisterCursor(CursorImpl cursor)
1189        throws DatabaseException {
1190
1191        synchronized (this) {
1192            CursorImpl prev = cursor.getLockerPrev();
1193            CursorImpl next = cursor.getLockerNext();
1194            if (prev == null) {
1195                cursorSet = next;
1196            } else {
1197                prev.setLockerNext(next);
1198            }
1199
1200            if (next != null) {
1201                next.setLockerPrev(prev);
1202            }
1203            cursor.setLockerPrev(null);
1204            cursor.setLockerNext(null);
1205        }
1206    }
1207
1208    /**
1209     * @return true if this txn is willing to give up the handle lock to
1210     * another txn before this txn ends.
1211     */

1212    public boolean isHandleLockTransferrable() {
1213        return false;
1214    }
1215
1216    /**
1217     * Check if all cursors associated with the txn are closed. If not, those
1218     * open cursors will be forcibly closed.
1219     * @return true if open cursors exist
1220     */

1221    private boolean checkCursorsForClose()
1222        throws DatabaseException {
1223
1224        CursorImpl c = cursorSet;
1225        while (c != null) {
1226            if (!c.isClosed()) {
1227                return true;
1228            }
1229            c = c.getLockerNext();
1230        }
1231
1232        return false;
1233    }
1234
1235    /**
1236     * stats
1237     */

1238    public LockStats collectStats(LockStats stats)
1239        throws DatabaseException {
1240
1241        synchronized (this) {
1242            int nReadLocks = (readLocks == null) ? 0 : readLocks.size();
1243            stats.setNReadLocks(stats.getNReadLocks() + nReadLocks);
1244            int nWriteLocks = (writeInfo == null) ? 0 : writeInfo.size();
1245            stats.setNWriteLocks(stats.getNWriteLocks() + nWriteLocks);
1246        }
1247
1248        return stats;
1249    }
1250
1251    /**
1252     * Set the state of a transaction to ONLY_ABORTABLE.
1253     */

1254    public void setOnlyAbortable() {
1255    txnState &= ~STATE_BITS;
1256    txnState |= ONLY_ABORTABLE;
1257    }
1258
1259    /**
1260     * Get the state of a transaction's ONLY_ABORTABLE.
1261     */

1262    public boolean getOnlyAbortable() {
1263    return (txnState & ONLY_ABORTABLE) != 0;
1264    }
1265
1266    /**
1267     * Throw an exception if the transaction is not open.
1268     *
1269     * If calledByAbort is true, it means we're being called
1270     * from abort().
1271     *
1272     * Caller must invoke with "this" synchronized.
1273     */

1274    protected void checkState(boolean calledByAbort)
1275        throws DatabaseException {
1276
1277        boolean ok = false;
1278    boolean onlyAbortable = false;
1279    byte state = (byte) (txnState & STATE_BITS);
1280    ok = (state == USABLE);
1281    onlyAbortable = (state == ONLY_ABORTABLE);
1282
1283    if (!calledByAbort && onlyAbortable) {
1284
1285        /*
1286         * It's ok for FindBugs to whine about id not being synchronized.
1287         */

1288            throw new DatabaseException
1289        ("Transaction " + id + " must be aborted.");
1290    }
1291
1292    if (ok ||
1293        (calledByAbort && onlyAbortable)) {
1294        return;
1295    }
1296
1297    /*
1298     * It's ok for FindBugs to whine about id not being synchronized.
1299     */

1300    throw new DatabaseException
1301        ("Transaction " + id + " has been closed.");
1302    }
1303
1304    /**
1305     */

1306    private void close(boolean isCommit)
1307        throws DatabaseException {
1308
1309        synchronized (this) {
1310        txnState &= ~STATE_BITS;
1311        txnState |= CLOSED;
1312        }
1313
1314        /*
1315         * UnregisterTxn must be called outside the synchronization on this
1316         * txn, because it gets the TxnManager's allTxns latch. The
1317         * checkpointer also gets the allTxns latch, and within that latch,
1318         * needs to synchronize on individual txns, so we must avoid a latching
1319         * hiearchy conflict.
1320         */

1321        envImpl.getTxnManager().unRegisterTxn(this, isCommit);
1322    }
1323
1324    /*
1325     * Log support
1326     */

1327
1328    /**
1329     * @see LogWritable#getLogSize
1330     */

1331    public int getLogSize() {
1332        return LOG_SIZE;
1333    }
1334
1335    /**
1336     * @see LogWritable#writeToLog
1337     */

1338    /*
1339     * It's ok for FindBugs to whine about id not being synchronized.
1340     */

1341    public void writeToLog(ByteBuffer JavaDoc logBuffer) {
1342        LogUtils.writeLong(logBuffer, id);
1343        LogUtils.writeLong(logBuffer, lastLoggedLsn);
1344    }
1345
1346    /**
1347     * @see LogReadable#readFromLog
1348     *
1349     * It's ok for FindBugs to whine about id not being synchronized.
1350     */

1351    public void readFromLog(ByteBuffer JavaDoc logBuffer, byte entryTypeVersion) {
1352        id = LogUtils.readLong(logBuffer);
1353        lastLoggedLsn = LogUtils.readLong(logBuffer);
1354    }
1355
1356    /**
1357     * @see LogReadable#dumpLog
1358     */

1359    public void dumpLog(StringBuffer JavaDoc sb, boolean verbose) {
1360        sb.append("<txn id=\"");
1361        sb.append(super.toString());
1362        sb.append("\">");
1363    sb.append(DbLsn.toString(lastLoggedLsn));
1364        sb.append("</txn>");
1365    }
1366
1367    /**
1368     * @see LogReadable#getTransactionId
1369     */

1370    public long getTransactionId() {
1371    return getId();
1372    }
1373
1374    /**
1375     * @see LogReadable#logEntryIsTransactional
1376     */

1377    public boolean logEntryIsTransactional() {
1378    return true;
1379    }
1380
1381    /**
1382     * Transfer a single handle lock to the set of corresponding handles at
1383     * commit time.
1384     */

1385    private void transferHandleLockToHandleSet(Long JavaDoc handleLockId,
1386                           Set JavaDoc dbHandleSet)
1387        throws DatabaseException {
1388
1389        /* Create a set of destination transactions */
1390        int numHandles = dbHandleSet.size();
1391        Database [] dbHandles = new Database[numHandles];
1392        dbHandles = (Database []) dbHandleSet.toArray(dbHandles);
1393        Locker [] destTxns = new Locker[numHandles];
1394        for (int i = 0; i < numHandles; i++) {
1395            destTxns[i] = new BasicLocker(envImpl);
1396        }
1397                
1398        /* Move this lock to the destination txns. */
1399        long nodeId = handleLockId.longValue();
1400        lockManager.transferMultiple(nodeId, this, destTxns);
1401
1402        for (int i = 0; i < numHandles; i++) {
1403
1404            /*
1405             * Make this handle and its handle protector txn remember each
1406             * other.
1407             */

1408            destTxns[i].addToHandleMaps(handleLockId, dbHandles[i]);
1409            DbInternal.dbSetHandleLocker(dbHandles[i], destTxns[i]);
1410        }
1411    }
1412
1413    /**
1414     * Send trace messages to the java.util.logger. Don't rely on the logger
1415     * alone to conditionalize whether we send this message, we don't even want
1416     * to construct the message if the level is not enabled. The string
1417     * construction can be numerous enough to show up on a performance profile.
1418     */

1419    private void traceCommit(int numWriteLocks, int numReadLocks) {
1420        Logger JavaDoc logger = envImpl.getLogger();
1421        if (logger.isLoggable(Level.FINE)) {
1422            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1423            sb.append(" Commit:id = ").append(id);
1424            sb.append(" numWriteLocks=").append(numWriteLocks);
1425            sb.append(" numReadLocks = ").append(numReadLocks);
1426            Tracer.trace(Level.FINE, envImpl, sb.toString());
1427        }
1428    }
1429
1430    int getInMemorySize() {
1431        return inMemorySize;
1432    }
1433
1434    /**
1435     * Store information about a DatabaseImpl that will have to be
1436     * purged at transaction commit or abort. This handles cleanup after
1437     * operations like Environment.truncateDatabase,
1438     * Environment.removeDatabase. Cleanup like this is done outside the
1439     * usual transaction commit or node undo processing, because
1440     * the mapping tree is always AutoTxn'ed to avoid deadlock and is
1441     * essentially non-transactional
1442     */

1443    private static class DatabaseCleanupInfo {
1444        DatabaseImpl dbImpl;
1445
1446        /* if true, clean on commit. If false, clean on abort. */
1447        boolean deleteAtCommit;
1448
1449        DatabaseCleanupInfo(DatabaseImpl dbImpl,
1450                            boolean deleteAtCommit) {
1451            this.dbImpl = dbImpl;
1452            this.deleteAtCommit = deleteAtCommit;
1453        }
1454    }
1455}
1456
Popular Tags