• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt/router/db-4.8.30/java/src/com/sleepycat/collections/
1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000-2009 Oracle.  All rights reserved.
5 *
6 * $Id$
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,
315                      CursorConfig cursorConfig,
316                      boolean writeCursor,
317                      Transaction txn)
318        throws DatabaseException {
319
320        if (cdbMode) {
321            CdbCursors cdbCursors = null;
322            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
323            if (cdbCursorsMap == null) {
324                cdbCursorsMap = new WeakHashMap();
325                localCdbCursors.set(cdbCursorsMap);
326            } else {
327                cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
328            }
329            if (cdbCursors == null) {
330                cdbCursors = new CdbCursors();
331                cdbCursorsMap.put(db, cdbCursors);
332            }
333
334            /*
335             * In CDB mode the cursorConfig specified by the user is ignored
336             * and only the writeCursor parameter is honored.  This is the only
337             * meaningful cursor attribute for CDB, and here we count on
338             * writeCursor flag being set correctly by the caller.
339             */
340            List cursors;
341            CursorConfig cdbConfig;
342            if (writeCursor) {
343                if (cdbCursors.readCursors.size() > 0) {
344
345                    /*
346                     * Although CDB allows opening a write cursor when a read
347                     * cursor is open, a self-deadlock will occur if a write is
348                     * attempted for a record that is read-locked; we should
349                     * avoid self-deadlocks at all costs
350                     */
351                    throw new IllegalStateException(
352                      "cannot open CDB write cursor when read cursor is open");
353                }
354                cursors = cdbCursors.writeCursors;
355                cdbConfig = new CursorConfig();
356                DbCompat.setWriteCursor(cdbConfig, true);
357            } else {
358                cursors = cdbCursors.readCursors;
359                cdbConfig = null;
360            }
361            Cursor cursor;
362            if (cursors.size() > 0) {
363                Cursor other = ((Cursor) cursors.get(0));
364                cursor = other.dup(false);
365            } else {
366                cursor = db.openCursor(null, cdbConfig);
367            }
368            cursors.add(cursor);
369            return cursor;
370        } else {
371            return db.openCursor(txn, cursorConfig);
372        }
373    }
374
375    /**
376     * Duplicates a cursor for a given database.
377     *
378     * @param writeCursor true to open a write cursor in a CDB environment, and
379     * ignored for other environments.
380     *
381     * @param samePosition is passed through to Cursor.dup().
382     *
383     * @return the open cursor.
384     *
385     * @throws DatabaseException if a database problem occurs.
386     */
387    Cursor dupCursor(Cursor cursor, boolean writeCursor, boolean samePosition)
388        throws DatabaseException {
389
390        if (cdbMode) {
391            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
392            if (cdbCursorsMap != null) {
393                Database db = cursor.getDatabase();
394                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
395                if (cdbCursors != null) {
396                    List cursors = writeCursor ? cdbCursors.writeCursors
397                                               : cdbCursors.readCursors;
398                    if (cursors.contains(cursor)) {
399                        Cursor newCursor = cursor.dup(samePosition);
400                        cursors.add(newCursor);
401                        return newCursor;
402                    }
403                }
404            }
405            throw new IllegalStateException("cursor to dup not tracked");
406        } else {
407            return cursor.dup(samePosition);
408        }
409    }
410
411    /**
412     * Closes a cursor.
413     *
414     * @param cursor the cursor to close.
415     *
416     * @throws DatabaseException if a database problem occurs.
417     */
418    void closeCursor(Cursor cursor)
419        throws DatabaseException {
420
421        if (cursor == null) {
422            return;
423        }
424        if (cdbMode) {
425            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
426            if (cdbCursorsMap != null) {
427                Database db = cursor.getDatabase();
428                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
429                if (cdbCursors != null) {
430                    if (cdbCursors.readCursors.remove(cursor) ||
431                        cdbCursors.writeCursors.remove(cursor)) {
432                        cursor.close();
433                        return;
434                    }
435                }
436            }
437            throw new IllegalStateException(
438              "closing CDB cursor that was not known to be open");
439        } else {
440            cursor.close();
441        }
442    }
443
444    /**
445     * Returns true if a CDB cursor is open and therefore a Database write
446     * operation should not be attempted since a self-deadlock may result.
447     */
448    boolean isCDBCursorOpen(Database db) {
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