001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2008 Oracle. All rights reserved.
005: *
006: * $Id: CurrentTransaction.java,v 1.46.2.5 2008/01/07 15:14:06 cwl Exp $
007: */
008:
009: package com.sleepycat.collections;
010:
011: import java.lang.ref.WeakReference;
012: import java.util.ArrayList;
013: import java.util.List;
014: import java.util.WeakHashMap;
015:
016: import com.sleepycat.compat.DbCompat;
017: import com.sleepycat.je.Cursor;
018: import com.sleepycat.je.CursorConfig;
019: import com.sleepycat.je.Database;
020: import com.sleepycat.je.DatabaseException;
021: import com.sleepycat.je.Environment;
022: import com.sleepycat.je.EnvironmentConfig;
023: import com.sleepycat.je.LockMode;
024: import com.sleepycat.je.Transaction;
025: import com.sleepycat.je.TransactionConfig;
026: import com.sleepycat.util.RuntimeExceptionWrapper;
027:
028: /**
029: * Provides access to the current transaction for the current thread within the
030: * context of a Berkeley DB environment. This class provides explicit
031: * transaction control beyond that provided by the {@link TransactionRunner}
032: * class. However, both methods of transaction control manage per-thread
033: * transactions.
034: *
035: * @author Mark Hayes
036: */
037: public class CurrentTransaction {
038:
039: /* For internal use, this class doubles as an Environment wrapper. */
040:
041: private static WeakHashMap envMap = new WeakHashMap();
042:
043: private LockMode writeLockMode;
044: private boolean cdbMode;
045: private boolean txnMode;
046: private boolean lockingMode;
047: private Environment env;
048: private ThreadLocal localTrans = new ThreadLocal();
049: private ThreadLocal localCdbCursors;
050:
051: /**
052: * Gets the CurrentTransaction accessor for a specified Berkeley DB
053: * environment. This method always returns the same reference when called
054: * more than once with the same environment parameter.
055: *
056: * @param env is an open Berkeley DB environment.
057: *
058: * @return the CurrentTransaction accessor for the given environment, or
059: * null if the environment is not transactional.
060: */
061: public static CurrentTransaction getInstance(Environment env) {
062:
063: CurrentTransaction currentTxn = getInstanceInternal(env);
064: return currentTxn.isTxnMode() ? currentTxn : null;
065: }
066:
067: /**
068: * Gets the CurrentTransaction accessor for a specified Berkeley DB
069: * environment. Unlike getInstance(), this method never returns null.
070: *
071: * @param env is an open Berkeley DB environment.
072: */
073: static CurrentTransaction getInstanceInternal(Environment env) {
074: synchronized (envMap) {
075: CurrentTransaction myEnv = null;
076: WeakReference myEnvRef = (WeakReference) envMap.get(env);
077: if (myEnvRef != null) {
078: myEnv = (CurrentTransaction) myEnvRef.get();
079: }
080: if (myEnv == null) {
081: myEnv = new CurrentTransaction(env);
082:
083: /*
084: * When a new CurrentTransaction instance is created, add
085: * a strong reference from the Environment to the
086: * CurrentTransaction instance so that it can not be GC'd
087: * until the Environment is GC'd (i.e. after it is closed).
088: * [#15721]
089: */
090: env.addReference(myEnv);
091: envMap.put(env, new WeakReference(myEnv));
092: }
093: return myEnv;
094: }
095: }
096:
097: private CurrentTransaction(Environment env) {
098: this .env = env;
099: try {
100: EnvironmentConfig config = env.getConfig();
101: txnMode = config.getTransactional();
102: lockingMode = DbCompat.getInitializeLocking(config);
103: if (txnMode || lockingMode) {
104: writeLockMode = LockMode.RMW;
105: } else {
106: writeLockMode = LockMode.DEFAULT;
107: }
108: cdbMode = DbCompat.getInitializeCDB(config);
109: if (cdbMode) {
110: localCdbCursors = new ThreadLocal();
111: }
112: } catch (DatabaseException e) {
113: throw new RuntimeExceptionWrapper(e);
114: }
115: }
116:
117: /**
118: * Returns whether environment is configured for locking.
119: */
120: final boolean isLockingMode() {
121:
122: return lockingMode;
123: }
124:
125: /**
126: * Returns whether this is a transactional environment.
127: */
128: final boolean isTxnMode() {
129:
130: return txnMode;
131: }
132:
133: /**
134: * Returns whether this is a Concurrent Data Store environment.
135: */
136: final boolean isCdbMode() {
137:
138: return cdbMode;
139: }
140:
141: /**
142: * Return the LockMode.RMW or null, depending on whether locking is
143: * enabled. LockMode.RMW will cause an error if passed when locking
144: * is not enabled. Locking is enabled if locking or transactions were
145: * specified for this environment.
146: */
147: final LockMode getWriteLockMode() {
148:
149: return writeLockMode;
150: }
151:
152: /**
153: * Returns the underlying Berkeley DB environment.
154: */
155: public final Environment getEnvironment() {
156:
157: return env;
158: }
159:
160: /**
161: * Returns the transaction associated with the current thread for this
162: * environment, or null if no transaction is active.
163: */
164: public final Transaction getTransaction() {
165:
166: Trans trans = (Trans) localTrans.get();
167: return (trans != null) ? trans.txn : null;
168: }
169:
170: /**
171: * Returns whether auto-commit may be performed by the collections API.
172: * True is returned if no collections API transaction is currently active,
173: * and no XA transaction is currently active.
174: */
175: boolean isAutoCommitAllowed() throws DatabaseException {
176:
177: return getTransaction() == null
178: && DbCompat.getThreadTransaction(env) == null;
179: }
180:
181: /**
182: * Begins a new transaction for this environment and associates it with
183: * the current thread. If a transaction is already active for this
184: * environment and thread, a nested transaction will be created.
185: *
186: * @param config the transaction configuration used for calling
187: * {@link Environment#beginTransaction}, or null to use the default
188: * configuration.
189: *
190: * @return the new transaction.
191: *
192: * @throws DatabaseException if the transaction cannot be started, in which
193: * case any existing transaction is not affected.
194: *
195: * @throws IllegalStateException if a transaction is already active and
196: * nested transactions are not supported by the environment.
197: */
198: public final Transaction beginTransaction(TransactionConfig config)
199: throws DatabaseException {
200:
201: Trans trans = (Trans) localTrans.get();
202: if (trans != null) {
203: if (trans.txn != null) {
204: if (!DbCompat.NESTED_TRANSACTIONS) {
205: throw new IllegalStateException(
206: "Nested transactions are not supported");
207: }
208: Transaction parentTxn = trans.txn;
209: trans = new Trans(trans, config);
210: trans.txn = env.beginTransaction(parentTxn, config);
211: localTrans.set(trans);
212: } else {
213: trans.txn = env.beginTransaction(null, config);
214: trans.config = config;
215: }
216: } else {
217: trans = new Trans(null, config);
218: trans.txn = env.beginTransaction(null, config);
219: localTrans.set(trans);
220: }
221: return trans.txn;
222: }
223:
224: /**
225: * Commits the transaction that is active for the current thread for this
226: * environment and makes the parent transaction (if any) the current
227: * transaction.
228: *
229: * @return the parent transaction or null if the committed transaction was
230: * not nested.
231: *
232: * @throws DatabaseException if an error occurs committing the transaction.
233: * The transaction will still be closed and the parent transaction will
234: * become the current transaction.
235: *
236: * @throws IllegalStateException if no transaction is active for the
237: * current thread for this environment.
238: */
239: public final Transaction commitTransaction()
240: throws DatabaseException, IllegalStateException {
241:
242: Trans trans = (Trans) localTrans.get();
243: if (trans != null && trans.txn != null) {
244: Transaction parent = closeTxn(trans);
245: trans.txn.commit();
246: return parent;
247: } else {
248: throw new IllegalStateException("No transaction is active");
249: }
250: }
251:
252: /**
253: * Aborts the transaction that is active for the current thread for this
254: * environment and makes the parent transaction (if any) the current
255: * transaction.
256: *
257: * @return the parent transaction or null if the aborted transaction was
258: * not nested.
259: *
260: * @throws DatabaseException if an error occurs aborting the transaction.
261: * The transaction will still be closed and the parent transaction will
262: * become the current transaction.
263: *
264: * @throws IllegalStateException if no transaction is active for the
265: * current thread for this environment.
266: */
267: public final Transaction abortTransaction()
268: throws DatabaseException, IllegalStateException {
269:
270: Trans trans = (Trans) localTrans.get();
271: if (trans != null && trans.txn != null) {
272: Transaction parent = closeTxn(trans);
273: trans.txn.abort();
274: return parent;
275: } else {
276: throw new IllegalStateException("No transaction is active");
277: }
278: }
279:
280: /**
281: * Returns whether the current transaction is a readUncommitted
282: * transaction.
283: */
284: final boolean isReadUncommitted() {
285:
286: Trans trans = (Trans) localTrans.get();
287: if (trans != null && trans.config != null) {
288: return trans.config.getReadUncommitted();
289: } else {
290: return false;
291: }
292: }
293:
294: private Transaction closeTxn(Trans trans) {
295:
296: localTrans.set(trans.parent);
297: return (trans.parent != null) ? trans.parent.txn : null;
298: }
299:
300: private static class Trans {
301:
302: private Trans parent;
303: private Transaction txn;
304: private TransactionConfig config;
305:
306: private Trans(Trans parent, TransactionConfig config) {
307:
308: this .parent = parent;
309: this .config = config;
310: }
311: }
312:
313: /**
314: * Opens a cursor for a given database, dup'ing an existing CDB cursor if
315: * one is open for the current thread.
316: */
317: Cursor openCursor(Database db, CursorConfig cursorConfig,
318: boolean writeCursor, Transaction txn)
319: throws DatabaseException {
320:
321: if (cdbMode) {
322: CdbCursors cdbCursors = null;
323: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
324: .get();
325: if (cdbCursorsMap == null) {
326: cdbCursorsMap = new WeakHashMap();
327: localCdbCursors.set(cdbCursorsMap);
328: } else {
329: cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
330: }
331: if (cdbCursors == null) {
332: cdbCursors = new CdbCursors();
333: cdbCursorsMap.put(db, cdbCursors);
334: }
335:
336: /*
337: * In CDB mode the cursorConfig specified by the user is ignored
338: * and only the writeCursor parameter is honored. This is the only
339: * meaningful cursor attribute for CDB, and here we count on
340: * writeCursor flag being set correctly by the caller.
341: */
342: List cursors;
343: CursorConfig cdbConfig;
344: if (writeCursor) {
345: if (cdbCursors.readCursors.size() > 0) {
346:
347: /*
348: * Although CDB allows opening a write cursor when a read
349: * cursor is open, a self-deadlock will occur if a write is
350: * attempted for a record that is read-locked; we should
351: * avoid self-deadlocks at all costs
352: */
353: throw new IllegalStateException(
354: "cannot open CDB write cursor when read cursor is open");
355: }
356: cursors = cdbCursors.writeCursors;
357: cdbConfig = new CursorConfig();
358: DbCompat.setWriteCursor(cdbConfig, true);
359: } else {
360: cursors = cdbCursors.readCursors;
361: cdbConfig = null;
362: }
363: Cursor cursor;
364: if (cursors.size() > 0) {
365: Cursor other = ((Cursor) cursors.get(0));
366: cursor = other.dup(false);
367: } else {
368: cursor = db.openCursor(null, cdbConfig);
369: }
370: cursors.add(cursor);
371: return cursor;
372: } else {
373: return db.openCursor(txn, cursorConfig);
374: }
375: }
376:
377: /**
378: * Duplicates a cursor for a given database.
379: *
380: * @param writeCursor true to open a write cursor in a CDB environment, and
381: * ignored for other environments.
382: *
383: * @param samePosition is passed through to Cursor.dup().
384: *
385: * @return the open cursor.
386: *
387: * @throws DatabaseException if a database problem occurs.
388: */
389: Cursor dupCursor(Cursor cursor, boolean writeCursor,
390: boolean samePosition) throws DatabaseException {
391:
392: if (cdbMode) {
393: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
394: .get();
395: if (cdbCursorsMap != null) {
396: Database db = cursor.getDatabase();
397: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
398: .get(db);
399: if (cdbCursors != null) {
400: List cursors = writeCursor ? cdbCursors.writeCursors
401: : cdbCursors.readCursors;
402: if (cursors.contains(cursor)) {
403: Cursor newCursor = cursor.dup(samePosition);
404: cursors.add(newCursor);
405: return newCursor;
406: }
407: }
408: }
409: throw new IllegalStateException("cursor to dup not tracked");
410: } else {
411: return cursor.dup(samePosition);
412: }
413: }
414:
415: /**
416: * Closes a cursor.
417: *
418: * @param cursor the cursor to close.
419: *
420: * @throws DatabaseException if a database problem occurs.
421: */
422: void closeCursor(Cursor cursor) throws DatabaseException {
423:
424: if (cursor == null) {
425: return;
426: }
427: if (cdbMode) {
428: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
429: .get();
430: if (cdbCursorsMap != null) {
431: Database db = cursor.getDatabase();
432: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
433: .get(db);
434: if (cdbCursors != null) {
435: if (cdbCursors.readCursors.remove(cursor)
436: || cdbCursors.writeCursors.remove(cursor)) {
437: cursor.close();
438: return;
439: }
440: }
441: }
442: throw new IllegalStateException(
443: "closing CDB cursor that was not known to be open");
444: } else {
445: cursor.close();
446: }
447: }
448:
449: /**
450: * Returns true if a CDB cursor is open and therefore a Database write
451: * operation should not be attempted since a self-deadlock may result.
452: */
453: boolean isCDBCursorOpen(Database db) throws DatabaseException {
454:
455: if (cdbMode) {
456: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
457: .get();
458: if (cdbCursorsMap != null) {
459: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
460: .get(db);
461:
462: if (cdbCursors != null
463: && (cdbCursors.readCursors.size() > 0 || cdbCursors.writeCursors
464: .size() > 0)) {
465: return true;
466: }
467: }
468: }
469: return false;
470: }
471:
472: static final class CdbCursors {
473:
474: List writeCursors = new ArrayList();
475: List readCursors = new ArrayList();
476: }
477: }
|