• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/db-4.7.25.NC/java/src/com/sleepycat/collections/
1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: CurrentTransaction.java,v 12.11 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.collections;
10
11import java.lang.ref.WeakReference;
12import java.util.ArrayList;
13import java.util.List;
14import java.util.WeakHashMap;
15
16import com.sleepycat.compat.DbCompat;
17import com.sleepycat.db.Cursor;
18import com.sleepycat.db.CursorConfig;
19import com.sleepycat.db.Database;
20import com.sleepycat.db.DatabaseException;
21import com.sleepycat.db.Environment;
22import com.sleepycat.db.EnvironmentConfig;
23import com.sleepycat.db.LockMode;
24import com.sleepycat.db.Transaction;
25import com.sleepycat.db.TransactionConfig;
26import com.sleepycat.util.RuntimeExceptionWrapper;
27
28/**
29 * Provides access to the current transaction for the current thread within the
30 * context of a Berkeley DB environment.  This class provides explicit
31 * transaction control beyond that provided by the {@link TransactionRunner}
32 * class.  However, both methods of transaction control manage per-thread
33 * transactions.
34 *
35 * @author Mark Hayes
36 */
37public class CurrentTransaction {
38
39    /* For internal use, this class doubles as an Environment wrapper. */
40
41    private static WeakHashMap<Environment,CurrentTransaction> envMap =
42        new WeakHashMap<Environment,CurrentTransaction>();
43
44    private LockMode writeLockMode;
45    private boolean cdbMode;
46    private boolean txnMode;
47    private boolean lockingMode;
48    private ThreadLocal localTrans = new ThreadLocal();
49    private ThreadLocal localCdbCursors;
50
51    /*
52     * Use a WeakReference to the Environment to avoid pinning the environment
53     * in the envMap.  The WeakHashMap envMap uses the Environment as a weak
54     * key, but this won't prevent GC of the Environment if the map's value has
55     * a hard reference to the Environment.  [#15444]
56     */
57    private WeakReference<Environment> envRef;
58
59    /**
60     * Gets the CurrentTransaction accessor for a specified Berkeley DB
61     * environment.  This method always returns the same reference when called
62     * more than once with the same environment parameter.
63     *
64     * @param env is an open Berkeley DB environment.
65     *
66     * @return the CurrentTransaction accessor for the given environment, or
67     * null if the environment is not transactional.
68     */
69    public static CurrentTransaction getInstance(Environment env) {
70
71        CurrentTransaction currentTxn = getInstanceInternal(env);
72        return currentTxn.isTxnMode() ? currentTxn : null;
73    }
74
75    /**
76     * Gets the CurrentTransaction accessor for a specified Berkeley DB
77     * environment.  Unlike getInstance(), this method never returns null.
78     *
79     * @param env is an open Berkeley DB environment.
80     */
81    static CurrentTransaction getInstanceInternal(Environment env) {
82        synchronized (envMap) {
83            CurrentTransaction ct = envMap.get(env);
84            if (ct == null) {
85                ct = new CurrentTransaction(env);
86                envMap.put(env, ct);
87            }
88            return ct;
89        }
90    }
91
92    private CurrentTransaction(Environment env) {
93        envRef = new WeakReference<Environment>(env);
94        try {
95            EnvironmentConfig config = env.getConfig();
96            txnMode = config.getTransactional();
97            lockingMode = DbCompat.getInitializeLocking(config);
98            if (txnMode || lockingMode) {
99                writeLockMode = LockMode.RMW;
100            } else {
101                writeLockMode = LockMode.DEFAULT;
102            }
103            cdbMode = DbCompat.getInitializeCDB(config);
104            if (cdbMode) {
105                localCdbCursors = new ThreadLocal();
106            }
107        } catch (DatabaseException e) {
108            throw new RuntimeExceptionWrapper(e);
109        }
110    }
111
112    /**
113     * Returns whether environment is configured for locking.
114     */
115    final boolean isLockingMode() {
116
117        return lockingMode;
118    }
119
120    /**
121     * Returns whether this is a transactional environment.
122     */
123    final boolean isTxnMode() {
124
125        return txnMode;
126    }
127
128    /**
129     * Returns whether this is a Concurrent Data Store environment.
130     */
131    final boolean isCdbMode() {
132
133        return cdbMode;
134    }
135
136    /**
137     * Return the LockMode.RMW or null, depending on whether locking is
138     * enabled.  LockMode.RMW will cause an error if passed when locking
139     * is not enabled.  Locking is enabled if locking or transactions were
140     * specified for this environment.
141     */
142    final LockMode getWriteLockMode() {
143
144        return writeLockMode;
145    }
146
147    /**
148     * Returns the underlying Berkeley DB environment.
149     */
150    public final Environment getEnvironment() {
151
152        return envRef.get();
153    }
154
155    /**
156     * Returns the transaction associated with the current thread for this
157     * environment, or null if no transaction is active.
158     */
159    public final Transaction getTransaction() {
160
161        Trans trans = (Trans) localTrans.get();
162        return (trans != null) ? trans.txn : null;
163    }
164
165    /**
166     * Returns whether auto-commit may be performed by the collections API.
167     * True is returned if no collections API transaction is currently active,
168     * and no XA transaction is currently active.
169     */
170    boolean isAutoCommitAllowed()
171	throws DatabaseException {
172
173        return getTransaction() == null &&
174               DbCompat.getThreadTransaction(getEnvironment()) == null;
175    }
176
177    /**
178     * Begins a new transaction for this environment and associates it with
179     * the current thread.  If a transaction is already active for this
180     * environment and thread, a nested transaction will be created.
181     *
182     * @param config the transaction configuration used for calling
183     * {@link Environment#beginTransaction}, or null to use the default
184     * configuration.
185     *
186     * @return the new transaction.
187     *
188     * @throws DatabaseException if the transaction cannot be started, in which
189     * case any existing transaction is not affected.
190     *
191     * @throws IllegalStateException if a transaction is already active and
192     * nested transactions are not supported by the environment.
193     */
194    public final Transaction beginTransaction(TransactionConfig config)
195        throws DatabaseException {
196
197        Environment env = getEnvironment();
198        Trans trans = (Trans) localTrans.get();
199        if (trans != null) {
200            if (trans.txn != null) {
201                if (!DbCompat.NESTED_TRANSACTIONS) {
202                    throw new IllegalStateException(
203                            "Nested transactions are not supported");
204                }
205                Transaction parentTxn = trans.txn;
206                trans = new Trans(trans, config);
207                trans.txn = env.beginTransaction(parentTxn, config);
208                localTrans.set(trans);
209            } else {
210                trans.txn = env.beginTransaction(null, config);
211                trans.config = config;
212            }
213        } else {
214            trans = new Trans(null, config);
215            trans.txn = env.beginTransaction(null, config);
216            localTrans.set(trans);
217        }
218        return trans.txn;
219    }
220
221    /**
222     * Commits the transaction that is active for the current thread for this
223     * environment and makes the parent transaction (if any) the current
224     * transaction.
225     *
226     * @return the parent transaction or null if the committed transaction was
227     * not nested.
228     *
229     * @throws DatabaseException if an error occurs committing the transaction.
230     * The transaction will still be closed and the parent transaction will
231     * become the current transaction.
232     *
233     * @throws IllegalStateException if no transaction is active for the
234     * current thread for this environment.
235     */
236    public final Transaction commitTransaction()
237        throws DatabaseException, IllegalStateException {
238
239        Trans trans = (Trans) localTrans.get();
240        if (trans != null && trans.txn != null) {
241            Transaction parent = closeTxn(trans);
242            trans.txn.commit();
243            return parent;
244        } else {
245            throw new IllegalStateException("No transaction is active");
246        }
247    }
248
249    /**
250     * Aborts the transaction that is active for the current thread for this
251     * environment and makes the parent transaction (if any) the current
252     * transaction.
253     *
254     * @return the parent transaction or null if the aborted transaction was
255     * not nested.
256     *
257     * @throws DatabaseException if an error occurs aborting the transaction.
258     * The transaction will still be closed and the parent transaction will
259     * become the current transaction.
260     *
261     * @throws IllegalStateException if no transaction is active for the
262     * current thread for this environment.
263     */
264    public final Transaction abortTransaction()
265        throws DatabaseException, IllegalStateException {
266
267        Trans trans = (Trans) localTrans.get();
268        if (trans != null && trans.txn != null) {
269            Transaction parent = closeTxn(trans);
270            trans.txn.abort();
271            return parent;
272        } else {
273            throw new IllegalStateException("No transaction is active");
274        }
275    }
276
277    /**
278     * Returns whether the current transaction is a readUncommitted
279     * transaction.
280     */
281    final boolean isReadUncommitted() {
282
283        Trans trans = (Trans) localTrans.get();
284        if (trans != null && trans.config != null) {
285            return trans.config.getReadUncommitted();
286        } else {
287            return false;
288        }
289    }
290
291    private Transaction closeTxn(Trans trans) {
292
293        localTrans.set(trans.parent);
294        return (trans.parent != null) ? trans.parent.txn : null;
295    }
296
297    private static class Trans {
298
299        private Trans parent;
300        private Transaction txn;
301        private TransactionConfig config;
302
303        private Trans(Trans parent, TransactionConfig config) {
304
305            this.parent = parent;
306            this.config = config;
307        }
308    }
309
310    /**
311     * Opens a cursor for a given database, dup'ing an existing CDB cursor if
312     * one is open for the current thread.
313     */
314    Cursor openCursor(Database db, CursorConfig cursorConfig,
315                      boolean writeCursor, Transaction txn)
316        throws DatabaseException {
317
318        if (cdbMode) {
319            CdbCursors cdbCursors = null;
320            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
321            if (cdbCursorsMap == null) {
322                cdbCursorsMap = new WeakHashMap();
323                localCdbCursors.set(cdbCursorsMap);
324            } else {
325                cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
326            }
327            if (cdbCursors == null) {
328                cdbCursors = new CdbCursors();
329                cdbCursorsMap.put(db, cdbCursors);
330            }
331
332            /*
333             * In CDB mode the cursorConfig specified by the user is ignored
334             * and only the writeCursor parameter is honored.  This is the only
335             * meaningful cursor attribute for CDB, and here we count on
336             * writeCursor flag being set correctly by the caller.
337             */
338            List cursors;
339            CursorConfig cdbConfig;
340            if (writeCursor) {
341                if (cdbCursors.readCursors.size() > 0) {
342
343                    /*
344                     * Although CDB allows opening a write cursor when a read
345                     * cursor is open, a self-deadlock will occur if a write is
346                     * attempted for a record that is read-locked; we should
347                     * avoid self-deadlocks at all costs
348                     */
349                    throw new IllegalStateException(
350                      "cannot open CDB write cursor when read cursor is open");
351                }
352                cursors = cdbCursors.writeCursors;
353                cdbConfig = new CursorConfig();
354                DbCompat.setWriteCursor(cdbConfig, true);
355            } else {
356                cursors = cdbCursors.readCursors;
357                cdbConfig = null;
358            }
359            Cursor cursor;
360            if (cursors.size() > 0) {
361                Cursor other = ((Cursor) cursors.get(0));
362                cursor = other.dup(false);
363            } else {
364                cursor = db.openCursor(null, cdbConfig);
365            }
366            cursors.add(cursor);
367            return cursor;
368        } else {
369            return db.openCursor(txn, cursorConfig);
370        }
371    }
372
373    /**
374     * Duplicates a cursor for a given database.
375     *
376     * @param writeCursor true to open a write cursor in a CDB environment, and
377     * ignored for other environments.
378     *
379     * @param samePosition is passed through to Cursor.dup().
380     *
381     * @return the open cursor.
382     *
383     * @throws DatabaseException if a database problem occurs.
384     */
385    Cursor dupCursor(Cursor cursor, boolean writeCursor, boolean samePosition)
386        throws DatabaseException {
387
388        if (cdbMode) {
389            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
390            if (cdbCursorsMap != null) {
391                Database db = cursor.getDatabase();
392                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
393                if (cdbCursors != null) {
394                    List cursors = writeCursor ? cdbCursors.writeCursors
395                                               : cdbCursors.readCursors;
396                    if (cursors.contains(cursor)) {
397                        Cursor newCursor = cursor.dup(samePosition);
398                        cursors.add(newCursor);
399                        return newCursor;
400                    }
401                }
402            }
403            throw new IllegalStateException("cursor to dup not tracked");
404        } else {
405            return cursor.dup(samePosition);
406        }
407    }
408
409    /**
410     * Closes a cursor.
411     *
412     * @param cursor the cursor to close.
413     *
414     * @throws DatabaseException if a database problem occurs.
415     */
416    void closeCursor(Cursor cursor)
417        throws DatabaseException {
418
419        if (cursor == null) {
420            return;
421        }
422        if (cdbMode) {
423            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
424            if (cdbCursorsMap != null) {
425                Database db = cursor.getDatabase();
426                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
427                if (cdbCursors != null) {
428                    if (cdbCursors.readCursors.remove(cursor) ||
429                        cdbCursors.writeCursors.remove(cursor)) {
430                        cursor.close();
431                        return;
432                    }
433                }
434            }
435            throw new IllegalStateException(
436              "closing CDB cursor that was not known to be open");
437        } else {
438            cursor.close();
439        }
440    }
441
442    /**
443     * Returns true if a CDB cursor is open and therefore a Database write
444     * operation should not be attempted since a self-deadlock may result.
445     */
446    boolean isCDBCursorOpen(Database db)
447        throws DatabaseException {
448
449        if (cdbMode) {
450            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
451            if (cdbCursorsMap != null) {
452                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
453
454                if (cdbCursors != null &&
455                    (cdbCursors.readCursors.size() > 0 ||
456                     cdbCursors.writeCursors.size() > 0)) {
457                    return true;
458                }
459            }
460        }
461        return false;
462    }
463
464    static final class CdbCursors {
465
466        List writeCursors = new ArrayList();
467        List readCursors = new ArrayList();
468    }
469}
470