From 3e44cc0515fc222fb4f43a3b624795863ba4429b Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 20 Jan 2008 21:42:57 +0000 Subject: Split TransactionManager and created TransactionScope class. --- .../com/amazon/carbonado/repo/jdbc/JDBCBlob.java | 4 +- .../com/amazon/carbonado/repo/jdbc/JDBCClob.java | 4 +- .../amazon/carbonado/repo/jdbc/JDBCRepository.java | 23 +- .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 10 +- .../repo/jdbc/JDBCTransactionManager.java | 13 +- .../amazon/carbonado/repo/sleepycat/BDBCursor.java | 18 +- .../carbonado/repo/sleepycat/BDBRepository.java | 19 +- .../carbonado/repo/sleepycat/BDBStorage.java | 82 +-- .../repo/sleepycat/BDBTransactionManager.java | 8 +- .../amazon/carbonado/spi/AbstractRepository.java | 72 +-- .../amazon/carbonado/spi/TransactionManager.java | 615 ++------------------- .../com/amazon/carbonado/spi/TransactionScope.java | 562 +++++++++++++++++++ 12 files changed, 726 insertions(+), 704 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/spi/TransactionScope.java (limited to 'src/main/java') diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java index ba5cdcf..69a8f3d 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java @@ -126,7 +126,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { throw new FetchException("Blob value is null"); } try { - JDBCTransaction txn = mRepo.localTxnManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnScope().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { if ((mBlob = mLoader.load(mRepo)) == null) { throw new PersistException("Blob value is null"); } - JDBCTransaction txn = mRepo.localTxnManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnScope().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java index c4e74f1..eedaf80 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java @@ -126,7 +126,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { throw new FetchException("Clob value is null"); } try { - JDBCTransaction txn = mRepo.localTxnManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnScope().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { if ((mClob = mLoader.load(mRepo)) == null) { throw new PersistException("Clob value is null"); } - JDBCTransaction txn = mRepo.localTxnManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnScope().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java index 0d93570..dd4e1ad 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java @@ -55,7 +55,7 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.sequence.SequenceCapability; import com.amazon.carbonado.sequence.SequenceValueProducer; import com.amazon.carbonado.spi.AbstractRepository; -import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.spi.TransactionScope; import com.amazon.carbonado.util.ThrowUnchecked; /** @@ -324,7 +324,7 @@ public class JDBCRepository extends AbstractRepository */ // Is called by auto-generated code and must be public. public boolean isTransactionForUpdate() { - return localTransactionManager().isForUpdate(); + return localTransactionScope().isForUpdate(); } /** @@ -419,7 +419,7 @@ public class JDBCRepository extends AbstractRepository throw new FetchException("Repository is closed"); } - JDBCTransaction txn = localTransactionManager().getTxn(); + JDBCTransaction txn = localTransactionScope().getTxn(); if (txn != null) { // Return the connection used by the current transaction. return txn.getConnection(); @@ -656,10 +656,6 @@ public class JDBCRepository extends AbstractRepository return mLog; } - protected TransactionManager createTransactionManager() { - return new JDBCTransactionManager(this); - } - protected Storage createStorage(Class type) throws RepositoryException { @@ -701,11 +697,12 @@ public class JDBCRepository extends AbstractRepository return mSupportStrategy.createSequenceValueProducer(name); } - /** - * Returns the thread-local JDBCTransactionManager, creating it if needed. - */ - // Provides access to transaction manager from other classes. - TransactionManager localTxnManager() { - return localTransactionManager(); + protected JDBCTransactionManager createTransactionManager() { + return new JDBCTransactionManager(this); + } + + // Provides access to transaction scope from other classes. + final TransactionScope localTxnScope() { + return localTransactionScope(); } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 3fbd302..e6fe277 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -213,7 +213,7 @@ class JDBCStorage extends StandardQueryFactory if (jblob != null) { try { - JDBCTransaction txn = mRepository.localTxnManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnScope().getTxn(); if (txn != null) { txn.register(jblob); } @@ -235,7 +235,7 @@ class JDBCStorage extends StandardQueryFactory if (jclob != null) { try { - JDBCTransaction txn = mRepository.localTxnManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnScope().getTxn(); if (txn != null) { txn.register(jclob); } @@ -606,7 +606,7 @@ class JDBCStorage extends StandardQueryFactory } public Cursor fetch(FilterValues values) throws FetchException { - boolean forUpdate = mRepository.localTxnManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnScope().isForUpdate(); Connection con = mRepository.getConnection(); try { PreparedStatement ps = con.prepareStatement(prepareSelect(values, forUpdate)); @@ -674,7 +674,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { indent(app, indentLevel); - boolean forUpdate = mRepository.localTxnManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnScope().isForUpdate(); app.append(prepareSelect(values, forUpdate)); app.append('\n'); return true; @@ -684,7 +684,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { try { - boolean forUpdate = mRepository.localTxnManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnScope().isForUpdate(); String statement = prepareSelect(values, forUpdate); return mRepository.getSupportStrategy().printPlan(app, indentLevel, statement); } catch (FetchException e) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java index a70e4c3..ca63331 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCTransactionManager.java @@ -29,8 +29,7 @@ import com.amazon.carbonado.Transaction; import com.amazon.carbonado.spi.TransactionManager; /** - * Manages transactions for JDBCRepository. Only one instance is allocated per - * thread. + * Manages transactions for JDBCRepository. * * @author Brian S O'Neill */ @@ -46,11 +45,6 @@ class JDBCTransactionManager extends TransactionManager { mRepositoryRef = new WeakReference(repository); } - @Override - public boolean isForUpdate() { - return super.isForUpdate() && mRepositoryRef.get().supportsSelectForUpdate(); - } - protected IsolationLevel selectIsolationLevel(Transaction parent, IsolationLevel level) { JDBCRepository repo = mRepositoryRef.get(); if (repo == null) { @@ -59,6 +53,11 @@ class JDBCTransactionManager extends TransactionManager { return repo.selectIsolationLevel(parent, level); } + protected boolean supportsForUpdate() { + JDBCRepository repo = mRepositoryRef.get(); + return repo != null && repo.supportsSelectForUpdate(); + } + protected JDBCTransaction createTxn(JDBCTransaction parent, IsolationLevel level) throws SQLException, FetchException { diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java index f16d2f7..b2a1f32 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java @@ -25,7 +25,7 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.raw.RawCursor; import com.amazon.carbonado.raw.RawUtil; -import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.spi.TransactionScope; /** * @@ -35,10 +35,10 @@ import com.amazon.carbonado.spi.TransactionManager; abstract class BDBCursor extends RawCursor { private static final byte[] NO_DATA = new byte[0]; - private final TransactionManager mTxnMgr; + private final TransactionScope mScope; private final BDBStorage mStorage; /** - * @param txnMgr + * @param scope * @param startBound specify the starting key for the cursor, or null if first * @param inclusiveStart true if start bound is inclusive * @param endBound specify the ending key for the cursor, or null if last @@ -50,7 +50,7 @@ abstract class BDBCursor extends RawCursor { * @throws ClassCastException if lock is not an object passed by * {@link BDBStorage#openCursor BDBStorage.openCursor} */ - protected BDBCursor(TransactionManager txnMgr, + protected BDBCursor(TransactionScope scope, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, @@ -58,19 +58,19 @@ abstract class BDBCursor extends RawCursor { BDBStorage storage) throws FetchException { - super(txnMgr.getLock(), + super(scope.getLock(), startBound, inclusiveStart, endBound, inclusiveEnd, maxPrefix, reverse); - mTxnMgr = txnMgr; + mScope = scope; mStorage = storage; - txnMgr.register(storage.getStorableType(), this); + scope.register(storage.getStorableType(), this); } void open() throws FetchException { try { - cursor_open(mTxnMgr.getTxn(), mTxnMgr.getIsolationLevel()); + cursor_open(mScope.getTxn(), mScope.getIsolationLevel()); } catch (Exception e) { throw mStorage.toFetchException(e); } @@ -80,7 +80,7 @@ abstract class BDBCursor extends RawCursor { try { super.close(); } finally { - mTxnMgr.unregister(mStorage.getStorableType(), this); + mScope.unregister(mStorage.getStorableType(), this); } } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java index 45fe85c..a0c549b 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java @@ -63,7 +63,7 @@ import com.amazon.carbonado.sequence.SequenceValueProducer; import com.amazon.carbonado.spi.AbstractRepository; import com.amazon.carbonado.spi.ExceptionTransformer; import com.amazon.carbonado.spi.LobEngine; -import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.spi.TransactionScope; /** * Repository implementation backed by a Berkeley DB. Data is encoded in the @@ -326,10 +326,6 @@ abstract class BDBRepository extends AbstractRepository return mLog; } - protected TransactionManager createTransactionManager() { - return new BDBTransactionManager(mExTransformer, this); - } - protected Storage createStorage(Class type) throws RepositoryException { @@ -346,6 +342,10 @@ abstract class BDBRepository extends AbstractRepository return new SequenceValueGenerator(BDBRepository.this, name); } + protected BDBTransactionManager createTransactionManager() { + return new BDBTransactionManager(mExTransformer, this); + } + /** * @see com.amazon.carbonado.spi.RepositoryBuilder#isMaster */ @@ -489,12 +489,9 @@ abstract class BDBRepository extends AbstractRepository return mExTransformer.toRepositoryException(e); } - /** - * Returns the thread-local BDBTransactionManager, creating it if needed. - */ - // Provides access to transaction manager from other classes. - TransactionManager localTxnManager() { - return localTransactionManager(); + // Provides access to transaction scope from other classes. + final TransactionScope localTxnScope() { + return localTransactionScope(); } /** diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java index 0b9f53c..77ea7d7 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java @@ -78,7 +78,7 @@ import com.amazon.carbonado.sequence.SequenceValueProducer; import com.amazon.carbonado.spi.IndexInfoImpl; import com.amazon.carbonado.spi.LobEngine; import com.amazon.carbonado.spi.StorableIndexSet; -import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.spi.TransactionScope; import com.amazon.carbonado.spi.TriggerManager; /** @@ -203,18 +203,18 @@ abstract class BDBStorage implements Storage, Storag } } - TransactionManager txnMgr = localTxnManager(); + TransactionScope scope = localTxnScope(); // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { try { - db_truncate(txnMgr.getTxn()); + db_truncate(scope.getTxn()); } catch (Exception e) { throw toPersistException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } @@ -317,7 +317,7 @@ abstract class BDBStorage implements Storage, Storag boolean reverseOrder) throws FetchException { - TransactionManager txnMgr = localTxnManager(); + TransactionScope scope = localTxnScope(); if (reverseRange) { { @@ -334,7 +334,7 @@ abstract class BDBStorage implements Storage, Storag } // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { StorableCodec codec = mStorableCodec; @@ -381,7 +381,7 @@ abstract class BDBStorage implements Storage, Storag try { BDBCursor cursor = openCursor - (txnMgr, + (scope, startBound, inclusiveStart, endBound, inclusiveEnd, mStorableCodec.getPrimaryKeyPrefixLength(), @@ -394,7 +394,7 @@ abstract class BDBStorage implements Storage, Storag throw toFetchException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } @@ -447,15 +447,15 @@ abstract class BDBStorage implements Storage, Storag boolean isPrimaryEmpty; try { - TransactionManager txnMgr = mRepository.localTxnManager(); + TransactionScope scope = mRepository.localTxnScope(); // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { primaryDatabase = env_openPrimaryDatabase(openTxn, databaseName); primaryInfo = registerPrimaryDatabase(readOnly, layout); - isPrimaryEmpty = db_isEmpty(null, primaryDatabase, txnMgr.isForUpdate()); + isPrimaryEmpty = db_isEmpty(null, primaryDatabase, scope.isForUpdate()); } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } catch (Exception e) { throw toRepositoryException(e); @@ -572,7 +572,7 @@ abstract class BDBStorage implements Storage, Storag } try { - Txn txn = mRepository.localTxnManager().getTxn(); + Txn txn = mRepository.localTxnScope().getTxn(); return db_compact(txn, mPrimaryDatabase, start, end); } catch (Exception e) { throw mRepository.toRepositoryException(e); @@ -637,7 +637,7 @@ abstract class BDBStorage implements Storage, Storag /** * @param txn optional transaction to commit when cursor is closed - * @param txnMgr + * @param scope * @param startBound specify the starting key for the cursor, or null if first * @param inclusiveStart true if start bound is inclusive * @param endBound specify the ending key for the cursor, or null if last @@ -647,7 +647,7 @@ abstract class BDBStorage implements Storage, Storag * @param database database to use */ protected abstract BDBCursor openCursor - (TransactionManager txnMgr, + (TransactionScope scope, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, @@ -667,8 +667,8 @@ abstract class BDBStorage implements Storage, Storag return mRepository.toRepositoryException(e); } - TransactionManager localTxnManager() { - return mRepository.localTxnManager(); + TransactionScope localTxnScope() { + return mRepository.localTxnScope(); } /** @@ -726,15 +726,15 @@ abstract class BDBStorage implements Storage, Storag * prevent threads from starting work that will likely fail along the way. */ void checkClosed() throws FetchException { - TransactionManager txnMgr = localTxnManager(); + TransactionScope scope = localTxnScope(); // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { if (mPrimaryDatabase == null) { // If shuting down, this will force us to block forever. try { - txnMgr.getTxn(); + scope.getTxn(); } catch (Exception e) { // Don't care. } @@ -742,20 +742,20 @@ abstract class BDBStorage implements Storage, Storag throw new FetchException("Repository closed"); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } void close() throws Exception { - TransactionManager txnMgr = mRepository.localTxnManager(); - txnMgr.getLock().lock(); + TransactionScope scope = mRepository.localTxnScope(); + scope.getLock().lock(); try { if (mPrimaryDatabase != null) { db_close(mPrimaryDatabase); mPrimaryDatabase = null; } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } @@ -998,18 +998,18 @@ abstract class BDBStorage implements Storage, Storag } public byte[] tryLoad(byte[] key) throws FetchException { - TransactionManager txnMgr = mStorage.localTxnManager(); + TransactionScope scope = mStorage.localTxnScope(); byte[] result; // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { try { - result = mStorage.db_get(txnMgr.getTxn(), key, txnMgr.isForUpdate()); + result = mStorage.db_get(scope.getTxn(), key, scope.isForUpdate()); } catch (Throwable e) { throw mStorage.toFetchException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } if (result == NOT_FOUND) { return null; @@ -1021,18 +1021,18 @@ abstract class BDBStorage implements Storage, Storag } public boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException { - TransactionManager txnMgr = mStorage.localTxnManager(); + TransactionScope scope = mStorage.localTxnScope(); Object result; // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { try { - result = mStorage.db_putNoOverwrite(txnMgr.getTxn(), key, value); + result = mStorage.db_putNoOverwrite(scope.getTxn(), key, value); } catch (Throwable e) { throw mStorage.toPersistException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } if (result == KEY_EXIST) { return false; @@ -1044,34 +1044,34 @@ abstract class BDBStorage implements Storage, Storag } public void store(S storable, byte[] key, byte[] value) throws PersistException { - TransactionManager txnMgr = mStorage.localTxnManager(); + TransactionScope scope = mStorage.localTxnScope(); // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { try { - if (!mStorage.db_put(txnMgr.getTxn(), key, value)) { + if (!mStorage.db_put(scope.getTxn(), key, value)) { throw new PersistException("Failed"); } } catch (Throwable e) { throw mStorage.toPersistException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } public boolean tryDelete(byte[] key) throws PersistException { - TransactionManager txnMgr = mStorage.localTxnManager(); + TransactionScope scope = mStorage.localTxnScope(); // Lock out shutdown task. - txnMgr.getLock().lock(); + scope.getLock().lock(); try { try { - return mStorage.db_delete(txnMgr.getTxn(), key); + return mStorage.db_delete(scope.getTxn(), key); } catch (Throwable e) { throw mStorage.toPersistException(e); } } finally { - txnMgr.getLock().unlock(); + scope.getLock().unlock(); } } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBTransactionManager.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBTransactionManager.java index f9b0c6c..5e0af3b 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBTransactionManager.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBTransactionManager.java @@ -29,9 +29,7 @@ import com.amazon.carbonado.spi.ExceptionTransformer; import com.amazon.carbonado.spi.TransactionManager; /** - * This class is used for tracking transactions and open cursors. Each - * thread that uses the BDBRepository instance is assigned at most one - * BDBTransactionManager instance. + * This class is used for tracking transactions and open cursors. * * @author Brian S O'Neill */ @@ -51,6 +49,10 @@ class BDBTransactionManager extends TransactionManager { return repository().selectIsolationLevel(parent, level); } + protected boolean supportsForUpdate() { + return true; + } + protected Txn createTxn(Txn parent, IsolationLevel level) throws Exception { if (level == IsolationLevel.NONE) { return null; diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java index 0dc1e74..7150f56 100644 --- a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java +++ b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java @@ -28,8 +28,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.logging.Log; -import org.cojen.util.WeakIdentityMap; - import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException; @@ -56,13 +54,9 @@ public abstract class AbstractRepository implements Repository, ShutdownCapability, SequenceCapability { private final String mName; + private final TransactionManager mTxnMgr; private final ReadWriteLock mShutdownLock; - private final ThreadLocal> mCurrentTxnMgr; - - // Weakly tracks all TransactionManager instances for shutdown hook. - private final Map, ?> mAllTxnMgrs; - private final StoragePool mStoragePool; private final SequenceValueProducerPool mSequencePool; @@ -74,10 +68,9 @@ public abstract class AbstractRepository if (name == null) { throw new IllegalArgumentException("Repository name cannot be null"); } + mName = name; mShutdownLock = new ReentrantReadWriteLock(); - mCurrentTxnMgr = new ThreadLocal>(); - mAllTxnMgrs = new WeakIdentityMap(); mStoragePool = new StoragePool() { protected Storage createStorage(Class type) @@ -104,6 +97,8 @@ public abstract class AbstractRepository } } }; + + mTxnMgr = createTransactionManager(); } public String getName() { @@ -117,19 +112,19 @@ public abstract class AbstractRepository } public Transaction enterTransaction() { - return localTransactionManager().enter(null); + return mTxnMgr.localTransactionScope().enter(null); } public Transaction enterTransaction(IsolationLevel level) { - return localTransactionManager().enter(level); + return mTxnMgr.localTransactionScope().enter(level); } public Transaction enterTopTransaction(IsolationLevel level) { - return localTransactionManager().enterTop(level); + return mTxnMgr.localTransactionScope().enterTop(level); } public IsolationLevel getTransactionIsolationLevel() { - return localTransactionManager().getIsolationLevel(); + return mTxnMgr.localTransactionScope().getIsolationLevel(); } /** @@ -212,21 +207,17 @@ public abstract class AbstractRepository } /** - * Returns the thread-local TransactionManager, creating it if needed. + * Returns the TransactionManager which was passed into the constructor. */ - protected TransactionManager localTransactionManager() { - TransactionManager txnMgr = mCurrentTxnMgr.get(); - if (txnMgr == null) { - lockoutShutdown(); - try { - txnMgr = createTransactionManager(); - mCurrentTxnMgr.set(txnMgr); - mAllTxnMgrs.put(txnMgr, null); - } finally { - unlockoutShutdown(); - } - } - return txnMgr; + protected TransactionManager transactionManager() { + return mTxnMgr; + } + + /** + * Returns the thread-local TransactionScope, creating it if needed. + */ + protected TransactionScope localTransactionScope() { + return mTxnMgr.localTransactionScope(); } /** @@ -278,11 +269,6 @@ public abstract class AbstractRepository */ protected abstract Log getLog(); - /** - * Called upon to create a new thread-local TransactionManager instance. - */ - protected abstract TransactionManager createTransactionManager(); - /** * Called upon to create a new Storage instance. */ @@ -295,6 +281,11 @@ public abstract class AbstractRepository protected abstract SequenceValueProducer createSequenceValueProducer(String name) throws RepositoryException; + /** + * Called upon to create a new TransactionManager instance. + */ + protected abstract TransactionManager createTransactionManager(); + void info(String message) { Log log = getLog(); if (log != null) { @@ -345,19 +336,10 @@ public abstract class AbstractRepository // Return unused sequence values. repository.mSequencePool.returnReservedValues(null); - // Close transactions and cursors. - for (TransactionManager txnMgr : repository.mAllTxnMgrs.keySet()) { - if (suspendThreads) { - // Lock transaction manager but don't release it. This - // prevents other threads from beginning work during - // shutdown, which will likely fail along the way. - txnMgr.getLock().lock(); - } - try { - txnMgr.close(); - } catch (Throwable e) { - repository.error("Failed to close TransactionManager", e); - } + try { + repository.mTxnMgr.close(suspendThreads); + } catch (Throwable e) { + repository.error("Failed to close TransactionManager", e); } repository.shutdownHook(); diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java index fe08a01..8820974 100644 --- a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java +++ b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java @@ -18,280 +18,82 @@ package com.amazon.carbonado.spi; -import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import com.amazon.carbonado.Cursor; -import com.amazon.carbonado.FetchException; +import org.cojen.util.WeakIdentityMap; + import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.RepositoryException; -import com.amazon.carbonado.Storable; import com.amazon.carbonado.Transaction; /** - * Generic transaction manager for repositories. Repositories should only have - * thread local instances. + * Generic transaction manager for repositories. * + * @param Transaction type * @author Brian S O'Neill */ public abstract class TransactionManager { + private static final int NOT_CLOSED = 0, CLOSED = 1, SUSPENDED = 2; - final Lock mLock; - final ExceptionTransformer mExTransformer; - - TransactionImpl mCurrent; - - // Tracks all registered cursors by storage type. - private Map, CursorList>> mCursors; + private final ThreadLocal> mCurrentScope; + private final Map, ?> mAllScopes; - private boolean mClosed; - - /** - * @deprecated - */ - public TransactionManager(ExceptionTransformer exTransformer) { - // The use of a fair lock is essential for shutdown hooks that attempt - // to acquire the locks of all TransactionManagers. Otherwise, the - // shutdown can take a long time. - mLock = new ReentrantLock(true); - mExTransformer = exTransformer; - } + private int mClosedState; public TransactionManager() { - this(null); - } - - /** - * Returns the exception transformer in use. - * - * @deprecated - */ - public ExceptionTransformer getExceptionTransformer() { - return mExTransformer; - } - - /** - * Enters a new transaction scope. - * - * @param level desired isolation level (may be null) - * @throws UnsupportedOperationException if isolation level higher than - * supported by repository - */ - public Transaction enter(IsolationLevel level) { - mLock.lock(); - try { - TransactionImpl parent = mCurrent; - IsolationLevel actualLevel = selectIsolationLevel(parent, level); - if (actualLevel == null) { - if (parent == null) { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level); - } else { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level - + "; parent isolation level: " + parent.getIsolationLevel()); - } - } - - return mCurrent = new TransactionImpl(this, parent, false, actualLevel); - } finally { - mLock.unlock(); - } + mCurrentScope = new ThreadLocal>(); + mAllScopes = new WeakIdentityMap(); } /** - * Enters a new top-level transaction scope. - * - * @param level desired isolation level (may be null) - * @throws UnsupportedOperationException if isolation level higher than - * supported by repository + * Returns the thread-local TransactionScope, creating it if needed. */ - public Transaction enterTop(IsolationLevel level) { - mLock.lock(); - try { - IsolationLevel actualLevel = selectIsolationLevel(null, level); - if (actualLevel == null) { - throw new UnsupportedOperationException - ("Desired isolation level not supported: " + level); + public TransactionScope localTransactionScope() { + TransactionScope scope = mCurrentScope.get(); + if (scope == null) { + int closedState; + synchronized (this) { + closedState = mClosedState; + scope = new TransactionScope(this, closedState != NOT_CLOSED); + mAllScopes.put(scope, null); } - - return mCurrent = new TransactionImpl(this, mCurrent, true, actualLevel); - } finally { - mLock.unlock(); - } - } - - /** - * Registers the given cursor against the current transaction, allowing - * it to be closed on transaction exit or transaction manager close. If - * there is no current transaction scope, the cursor is registered as not - * part of a transaction. Cursors should register when created. - */ - public void register(Class type, Cursor cursor) { - mLock.lock(); - try { - checkState(); - if (mCursors == null) { - mCursors = new IdentityHashMap, CursorList>>(); + mCurrentScope.set(scope); + if (closedState == SUSPENDED) { + // Immediately suspend new scope. + scope.getLock().lock(); } - - CursorList> cursorList = mCursors.get(type); - if (cursorList == null) { - cursorList = new CursorList>(); - mCursors.put(type, cursorList); - } - - cursorList.register(cursor, mCurrent); - - if (mCurrent != null) { - mCurrent.register(cursor); - } - } finally { - mLock.unlock(); } + return scope; } /** - * Unregisters a previously registered cursor. Cursors should unregister - * when closed. - */ - public void unregister(Class type, Cursor cursor) { - mLock.lock(); - try { - if (mCursors != null) { - CursorList> cursorList = mCursors.get(type); - if (cursorList != null) { - TransactionImpl txnImpl = cursorList.unregister(cursor); - if (txnImpl != null) { - txnImpl.unregister(cursor); - } - } - } - } finally { - mLock.unlock(); - } - } - - /** - * Returns the count of registered cursors of a specific type. - */ - public int getRegisteredCount(Class type) { - mLock.lock(); - try { - if (mCursors != null) { - CursorList> cursorList = mCursors.get(type); - if (cursorList != null) { - return cursorList.size(); - } - } - } finally { - mLock.unlock(); - } - return 0; - } - - /** - * Returns a registered cursor of the given type, or null if none at given index. - */ - @SuppressWarnings("unchecked") - public Cursor getRegisteredCursor(Class type, int index) { - mLock.lock(); - try { - if (mCursors != null) { - CursorList> cursorList = mCursors.get(type); - if (cursorList != null) { - if (index < cursorList.size()) { - return (Cursor) cursorList.getCursor(index); - } - } - } - } finally { - mLock.unlock(); - } - return null; - } - - /** - * Returns lock used by TransactionManager. While holding lock, operations - * are suspended. - */ - public Lock getLock() { - return mLock; - } - - /** - * Exits all transactions and closes all cursors. Should be called only - * when repository is closed. - */ - public void close() throws RepositoryException { - mLock.lock(); - try { - if (!mClosed) { - while (mCurrent != null) { - mCurrent.exit(); - } - if (mCursors != null) { - for (CursorList> cursorList : mCursors.values()) { - cursorList.closeCursors(); - } - } - } - } finally { - mClosed = true; - mLock.unlock(); - } - } - - /** - * Returns null if no transaction is in progress. + * Closes all transaction scopes. Should be called only when repository is + * closed. * - * @throws Exception thrown by createTxn or reuseTxn + * @param suspend when true, indefinitely suspend all threads interacting + * with transactions */ - public Txn getTxn() throws Exception { - mLock.lock(); - try { - checkState(); - return mCurrent == null ? null : mCurrent.getTxn(); - } finally { - mLock.unlock(); + public synchronized void close(boolean suspend) throws RepositoryException { + if (mClosedState == SUSPENDED) { + // If suspended, attempting to close again will likely deadlock. + return; } - } - /** - * Returns true if a transaction is in progress and it is for update. - */ - public boolean isForUpdate() { - mLock.lock(); - try { - return (mClosed || mCurrent == null) ? false : mCurrent.isForUpdate(); - } finally { - mLock.unlock(); + if (suspend) { + for (TransactionScope scope : mAllScopes.keySet()) { + // Lock scope but don't release it. This prevents other threads + // from beginning work during shutdown, which will likely fail + // along the way. + scope.getLock().lock(); + } } - } - /** - * Returns the isolation level of the current transaction, or null if there - * is no transaction in the current thread. - */ - public IsolationLevel getIsolationLevel() { - mLock.lock(); - try { - return (mClosed || mCurrent == null) ? null : mCurrent.getIsolationLevel(); - } finally { - mLock.unlock(); - } - } + mClosedState = suspend ? SUSPENDED : CLOSED; - /** - * Caller must hold mLock. - */ - private void checkState() { - if (mClosed) { - throw new IllegalStateException("Repository is closed"); + for (TransactionScope scope : mAllScopes.keySet()) { + scope.close(); } } @@ -305,6 +107,13 @@ public abstract class TransactionManager { protected abstract IsolationLevel selectIsolationLevel(Transaction parent, IsolationLevel level); + /** + * Return true if transactions support "for update" mode. + * + * @since 1.2 + */ + protected abstract boolean supportsForUpdate(); + /** * Creates an internal transaction representation, with the optional parent * transaction. If parent is not null and real nested transactions are not @@ -363,330 +172,4 @@ public abstract class TransactionManager { * Aborts and closes the given internal transaction. */ protected abstract void abortTxn(Txn txn) throws PersistException; - - private static class TransactionImpl implements Transaction { - private final TransactionManager mTxnMgr; - private final TransactionImpl mParent; - private final boolean mTop; - private final IsolationLevel mLevel; - - private boolean mForUpdate; - private int mDesiredLockTimeout; - private TimeUnit mTimeoutUnit; - - private TransactionImpl mChild; - private boolean mExited; - private Txn mTxn; - - // Tracks all registered cursors. - private CursorList mCursorList; - - TransactionImpl(TransactionManager txnMgr, - TransactionImpl parent, - boolean top, - IsolationLevel level) { - mTxnMgr = txnMgr; - mParent = parent; - mTop = top; - mLevel = level; - if (!top && parent != null) { - parent.mChild = this; - mDesiredLockTimeout = parent.mDesiredLockTimeout; - mTimeoutUnit = parent.mTimeoutUnit; - } - } - - public void commit() throws PersistException { - TransactionManager txnMgr = mTxnMgr; - txnMgr.mLock.lock(); - try { - if (!mExited) { - if (mChild != null) { - mChild.commit(); - } - - closeCursors(); - - if (mTxn != null) { - if (mParent == null || mParent.mTxn != mTxn) { - try { - if (!txnMgr.commitTxn(mTxn)) { - mTxn = null; - } - } catch (Throwable e) { - mTxn = null; - if (txnMgr.mExTransformer != null) { - throw txnMgr.mExTransformer.toPersistException(e); - } - throw ExceptionTransformer.getInstance().toPersistException(e); - } - } else { - // Indicate fake nested transaction committed. - mTxn = null; - } - } - } - } finally { - txnMgr.mLock.unlock(); - } - } - - public void exit() throws PersistException { - TransactionManager txnMgr = mTxnMgr; - txnMgr.mLock.lock(); - try { - if (!mExited) { - if (mChild != null) { - mChild.exit(); - } - - closeCursors(); - - if (mTxn != null) { - try { - if (mParent == null || mParent.mTxn != mTxn) { - try { - txnMgr.abortTxn(mTxn); - } catch (Throwable e) { - if (txnMgr.mExTransformer != null) { - throw txnMgr.mExTransformer.toPersistException(e); - } - throw ExceptionTransformer.getInstance().toPersistException(e); - } - } - } finally { - mTxn = null; - } - } - - txnMgr.mCurrent = mParent; - - mExited = true; - } - } finally { - txnMgr.mLock.unlock(); - } - } - - public void setForUpdate(boolean forUpdate) { - mForUpdate = forUpdate; - } - - public boolean isForUpdate() { - return mForUpdate; - } - - public void setDesiredLockTimeout(int timeout, TimeUnit unit) { - TransactionManager txnMgr = mTxnMgr; - txnMgr.mLock.lock(); - try { - if (timeout < 0) { - mDesiredLockTimeout = 0; - mTimeoutUnit = null; - } else { - mDesiredLockTimeout = timeout; - mTimeoutUnit = unit; - } - } finally { - txnMgr.mLock.unlock(); - } - } - - public IsolationLevel getIsolationLevel() { - return mLevel; - } - - // Caller must hold mLock. - void register(Cursor cursor) { - if (mCursorList == null) { - mCursorList = new CursorList(); - } - mCursorList.register(cursor, null); - } - - // Caller must hold mLock. - void unregister(Cursor cursor) { - if (mCursorList != null) { - mCursorList.unregister(cursor); - } - } - - // Caller must hold mLock. - Txn getTxn() throws Exception { - TransactionManager txnMgr = mTxnMgr; - if (mTxn != null) { - txnMgr.reuseTxn(mTxn); - } else { - Txn parentTxn; - if (mParent == null || mTop) { - parentTxn = null; - } else if ((parentTxn = mParent.mTxn) == null) { - // No point in creating nested transaction if parent - // has never been used. Create parent transaction - // and use it in child transaction, just like a fake - // nested transaction. - if ((parentTxn = mParent.getTxn()) != null) { - return mTxn = parentTxn; - } - // Isolation level of parent is none, so proceed to create - // a real transaction. - } - if (mTimeoutUnit == null) { - mTxn = txnMgr.createTxn(parentTxn, mLevel); - } else { - mTxn = txnMgr.createTxn(parentTxn, mLevel, mDesiredLockTimeout, mTimeoutUnit); - } - } - return mTxn; - } - - // Caller must hold mLock. - private void closeCursors() throws PersistException { - if (mCursorList != null) { - mCursorList.closeCursors(); - } - } - } - - /** - * Simple fast list/map for holding a small amount of cursors. - */ - static class CursorList { - private int mSize; - private Cursor[] mCursors; - private V[] mValues; - - CursorList() { - mCursors = new Cursor[8]; - } - - /** - * @param value optional value to associate - */ - @SuppressWarnings("unchecked") - void register(Cursor cursor, V value) { - int size = mSize; - Cursor[] cursors = mCursors; - - if (size == cursors.length) { - int newLength = size << 1; - - Cursor[] newCursors = new Cursor[newLength]; - System.arraycopy(cursors, 0, newCursors, 0, size); - mCursors = cursors = newCursors; - - if (mValues != null) { - V[] newValues = (V[]) new Object[newLength]; - System.arraycopy(mValues, 0, newValues, 0, size); - mValues = newValues; - } - } - - cursors[size] = cursor; - - if (value != null) { - V[] values = mValues; - if (values == null) { - mValues = values = (V[]) new Object[cursors.length]; - } - values[size] = value; - } - - mSize = size + 1; - } - - V unregister(Cursor cursor) { - // Assuming that cursors are opened and closed in LIFO order - // (stack order), search backwards to optimize. - Cursor[] cursors = mCursors; - int size = mSize; - int i = size; - search: { - while (--i >= 0) { - if (cursors[i] == cursor) { - break search; - } - } - // Not found. - return null; - } - - V[] values = mValues; - V value; - - if (values == null) { - value = null; - if (i == size - 1) { - // Clear reference so that it can be garbage collected. - cursors[i] = null; - } else { - // Shift array elements down. - System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); - } - } else { - value = values[i]; - if (i == size - 1) { - // Clear references so that they can be garbage collected. - cursors[i] = null; - values[i] = null; - } else { - // Shift array elements down. - System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); - System.arraycopy(values, i + 1, values, i, size - i - 1); - } - } - - mSize = size - 1; - return value; - } - - int size() { - return mSize; - } - - Cursor getCursor(int index) { - return mCursors[index]; - } - - V getValue(int index) { - V[] values = mValues; - return values == null ? null : values[index]; - } - - /** - * Closes all cursors and resets the size of this list to 0. - */ - void closeCursors() throws PersistException { - // Note: Iteration must be in reverse order. Calling close on the - // cursor should cause it to unregister from this list. This will - // cause only a modification to the end of the list, which is no - // longer needed by this method. - try { - Cursor[] cursors = mCursors; - V[] values = mValues; - int i = mSize; - if (values == null) { - while (--i >= 0) { - Cursor cursor = cursors[i]; - if (cursor != null) { - cursor.close(); - cursors[i] = null; - } - } - } else { - while (--i >= 0) { - Cursor cursor = cursors[i]; - if (cursor != null) { - cursor.close(); - cursors[i] = null; - values[i] = null; - } - } - } - } catch (FetchException e) { - throw e.toPersistException(); - } - mSize = 0; - } - } } diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java new file mode 100644 index 0000000..2031d68 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java @@ -0,0 +1,562 @@ +/* + * Copyright 2008 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.spi; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.amazon.carbonado.Cursor; +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Transaction; + +/** + * Container of thread local, scoped transactions. + * + * @param Transaction type + * @author Brian S O'Neill + * @since 1.2 + * @see TransactionManager + */ +public class TransactionScope { + final TransactionManager mTxnMgr; + final Lock mLock; + + TransactionImpl mCurrent; + + // Tracks all registered cursors by storage type. + private Map, CursorList>> mCursors; + + private boolean mClosed; + + TransactionScope(TransactionManager txnMgr, boolean closed) { + mTxnMgr = txnMgr; + mLock = new ReentrantLock(true); + mClosed = closed; + } + + /** + * Enters a new transaction scope. + * + * @param level desired isolation level (may be null) + * @throws UnsupportedOperationException if isolation level higher than + * supported by repository + */ + public Transaction enter(IsolationLevel level) { + mLock.lock(); + try { + TransactionImpl parent = mCurrent; + IsolationLevel actualLevel = mTxnMgr.selectIsolationLevel(parent, level); + if (actualLevel == null) { + if (parent == null) { + throw new UnsupportedOperationException + ("Desired isolation level not supported: " + level); + } else { + throw new UnsupportedOperationException + ("Desired isolation level not supported: " + level + + "; parent isolation level: " + parent.getIsolationLevel()); + } + } + + return mCurrent = new TransactionImpl(this, parent, false, actualLevel); + } finally { + mLock.unlock(); + } + } + + /** + * Enters a new top-level transaction scope. + * + * @param level desired isolation level (may be null) + * @throws UnsupportedOperationException if isolation level higher than + * supported by repository + */ + public Transaction enterTop(IsolationLevel level) { + mLock.lock(); + try { + IsolationLevel actualLevel = mTxnMgr.selectIsolationLevel(null, level); + if (actualLevel == null) { + throw new UnsupportedOperationException + ("Desired isolation level not supported: " + level); + } + + return mCurrent = new TransactionImpl(this, mCurrent, true, actualLevel); + } finally { + mLock.unlock(); + } + } + + /** + * Registers the given cursor against the current transaction, allowing + * it to be closed on transaction exit or transaction manager close. If + * there is no current transaction scope, the cursor is registered as not + * part of a transaction. Cursors should register when created. + */ + public void register(Class type, Cursor cursor) { + mLock.lock(); + try { + checkState(); + if (mCursors == null) { + mCursors = new IdentityHashMap, CursorList>>(); + } + + CursorList> cursorList = mCursors.get(type); + if (cursorList == null) { + cursorList = new CursorList>(); + mCursors.put(type, cursorList); + } + + cursorList.register(cursor, mCurrent); + + if (mCurrent != null) { + mCurrent.register(cursor); + } + } finally { + mLock.unlock(); + } + } + + /** + * Unregisters a previously registered cursor. Cursors should unregister + * when closed. + */ + public void unregister(Class type, Cursor cursor) { + mLock.lock(); + try { + if (mCursors != null) { + CursorList> cursorList = mCursors.get(type); + if (cursorList != null) { + TransactionImpl txnImpl = cursorList.unregister(cursor); + if (txnImpl != null) { + txnImpl.unregister(cursor); + } + } + } + } finally { + mLock.unlock(); + } + } + + /** + * Returns lock used by TransactionScope. While holding lock, operations + * are suspended. + */ + public Lock getLock() { + return mLock; + } + + /** + * Exits all transactions and closes all cursors. Should be called only + * when repository is closed. + */ + public void close() throws RepositoryException { + mLock.lock(); + try { + if (!mClosed) { + while (mCurrent != null) { + mCurrent.exit(); + } + if (mCursors != null) { + for (CursorList> cursorList : mCursors.values()) { + cursorList.closeCursors(); + } + } + } + } finally { + mClosed = true; + mLock.unlock(); + } + } + + /** + * Returns null if no transaction is in progress. + * + * @throws Exception thrown by createTxn or reuseTxn + */ + public Txn getTxn() throws Exception { + mLock.lock(); + try { + checkState(); + return mCurrent == null ? null : mCurrent.getTxn(); + } finally { + mLock.unlock(); + } + } + + /** + * Returns true if a transaction is in progress and it is for update. + */ + public boolean isForUpdate() { + mLock.lock(); + try { + return (mClosed || mCurrent == null) ? false : mCurrent.isForUpdate(); + } finally { + mLock.unlock(); + } + } + + /** + * Returns the isolation level of the current transaction, or null if there + * is no transaction in the current thread. + */ + public IsolationLevel getIsolationLevel() { + mLock.lock(); + try { + return (mClosed || mCurrent == null) ? null : mCurrent.getIsolationLevel(); + } finally { + mLock.unlock(); + } + } + + /** + * Caller must hold mLock. + */ + private void checkState() { + if (mClosed) { + throw new IllegalStateException("Repository is closed"); + } + } + + private static class TransactionImpl implements Transaction { + private final TransactionScope mScope; + private final TransactionImpl mParent; + private final boolean mTop; + private final IsolationLevel mLevel; + + private boolean mForUpdate; + private int mDesiredLockTimeout; + private TimeUnit mTimeoutUnit; + + private TransactionImpl mChild; + private boolean mExited; + private Txn mTxn; + + // Tracks all registered cursors. + private CursorList mCursorList; + + TransactionImpl(TransactionScope scope, + TransactionImpl parent, + boolean top, + IsolationLevel level) { + mScope = scope; + mParent = parent; + mTop = top; + mLevel = level; + if (!top && parent != null) { + parent.mChild = this; + mDesiredLockTimeout = parent.mDesiredLockTimeout; + mTimeoutUnit = parent.mTimeoutUnit; + } + } + + public void commit() throws PersistException { + TransactionScope scope = mScope; + scope.mLock.lock(); + try { + if (!mExited) { + if (mChild != null) { + mChild.commit(); + } + + closeCursors(); + + if (mTxn != null) { + if (mParent == null || mParent.mTxn != mTxn) { + try { + if (!scope.mTxnMgr.commitTxn(mTxn)) { + mTxn = null; + } + } catch (Throwable e) { + mTxn = null; + throw ExceptionTransformer.getInstance().toPersistException(e); + } + } else { + // Indicate fake nested transaction committed. + mTxn = null; + } + } + } + } finally { + scope.mLock.unlock(); + } + } + + public void exit() throws PersistException { + TransactionScope scope = mScope; + scope.mLock.lock(); + try { + if (!mExited) { + if (mChild != null) { + mChild.exit(); + } + + closeCursors(); + + if (mTxn != null) { + try { + if (mParent == null || mParent.mTxn != mTxn) { + try { + scope.mTxnMgr.abortTxn(mTxn); + } catch (Throwable e) { + throw ExceptionTransformer.getInstance().toPersistException(e); + } + } + } finally { + mTxn = null; + } + } + + scope.mCurrent = mParent; + + mExited = true; + } + } finally { + scope.mLock.unlock(); + } + } + + public void setForUpdate(boolean forUpdate) { + mForUpdate = forUpdate && mScope.mTxnMgr.supportsForUpdate(); + } + + public boolean isForUpdate() { + return mForUpdate; + } + + public void setDesiredLockTimeout(int timeout, TimeUnit unit) { + TransactionScope scope = mScope; + scope.mLock.lock(); + try { + if (timeout < 0) { + mDesiredLockTimeout = 0; + mTimeoutUnit = null; + } else { + mDesiredLockTimeout = timeout; + mTimeoutUnit = unit; + } + } finally { + scope.mLock.unlock(); + } + } + + public IsolationLevel getIsolationLevel() { + return mLevel; + } + + // Caller must hold mLock. + void register(Cursor cursor) { + if (mCursorList == null) { + mCursorList = new CursorList(); + } + mCursorList.register(cursor, null); + } + + // Caller must hold mLock. + void unregister(Cursor cursor) { + if (mCursorList != null) { + mCursorList.unregister(cursor); + } + } + + // Caller must hold mLock. + Txn getTxn() throws Exception { + TransactionScope scope = mScope; + if (mTxn != null) { + scope.mTxnMgr.reuseTxn(mTxn); + } else { + Txn parentTxn; + if (mParent == null || mTop) { + parentTxn = null; + } else if ((parentTxn = mParent.mTxn) == null) { + // No point in creating nested transaction if parent + // has never been used. Create parent transaction + // and use it in child transaction, just like a fake + // nested transaction. + if ((parentTxn = mParent.getTxn()) != null) { + return mTxn = parentTxn; + } + // Isolation level of parent is none, so proceed to create + // a real transaction. + } + if (mTimeoutUnit == null) { + mTxn = scope.mTxnMgr.createTxn(parentTxn, mLevel); + } else { + mTxn = scope.mTxnMgr.createTxn(parentTxn, mLevel, + mDesiredLockTimeout, mTimeoutUnit); + } + } + return mTxn; + } + + // Caller must hold mLock. + private void closeCursors() throws PersistException { + if (mCursorList != null) { + mCursorList.closeCursors(); + } + } + } + + /** + * Simple fast list/map for holding a small amount of cursors. + */ + static class CursorList { + private int mSize; + private Cursor[] mCursors; + private V[] mValues; + + CursorList() { + mCursors = new Cursor[8]; + } + + /** + * @param value optional value to associate + */ + @SuppressWarnings("unchecked") + void register(Cursor cursor, V value) { + int size = mSize; + Cursor[] cursors = mCursors; + + if (size == cursors.length) { + int newLength = size << 1; + + Cursor[] newCursors = new Cursor[newLength]; + System.arraycopy(cursors, 0, newCursors, 0, size); + mCursors = cursors = newCursors; + + if (mValues != null) { + V[] newValues = (V[]) new Object[newLength]; + System.arraycopy(mValues, 0, newValues, 0, size); + mValues = newValues; + } + } + + cursors[size] = cursor; + + if (value != null) { + V[] values = mValues; + if (values == null) { + mValues = values = (V[]) new Object[cursors.length]; + } + values[size] = value; + } + + mSize = size + 1; + } + + V unregister(Cursor cursor) { + // Assuming that cursors are opened and closed in LIFO order + // (stack order), search backwards to optimize. + Cursor[] cursors = mCursors; + int size = mSize; + int i = size; + search: { + while (--i >= 0) { + if (cursors[i] == cursor) { + break search; + } + } + // Not found. + return null; + } + + V[] values = mValues; + V value; + + if (values == null) { + value = null; + if (i == size - 1) { + // Clear reference so that it can be garbage collected. + cursors[i] = null; + } else { + // Shift array elements down. + System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); + } + } else { + value = values[i]; + if (i == size - 1) { + // Clear references so that they can be garbage collected. + cursors[i] = null; + values[i] = null; + } else { + // Shift array elements down. + System.arraycopy(cursors, i + 1, cursors, i, size - i - 1); + System.arraycopy(values, i + 1, values, i, size - i - 1); + } + } + + mSize = size - 1; + return value; + } + + int size() { + return mSize; + } + + Cursor getCursor(int index) { + return mCursors[index]; + } + + V getValue(int index) { + V[] values = mValues; + return values == null ? null : values[index]; + } + + /** + * Closes all cursors and resets the size of this list to 0. + */ + void closeCursors() throws PersistException { + // Note: Iteration must be in reverse order. Calling close on the + // cursor should cause it to unregister from this list. This will + // cause only a modification to the end of the list, which is no + // longer needed by this method. + try { + Cursor[] cursors = mCursors; + V[] values = mValues; + int i = mSize; + if (values == null) { + while (--i >= 0) { + Cursor cursor = cursors[i]; + if (cursor != null) { + cursor.close(); + cursors[i] = null; + } + } + } else { + while (--i >= 0) { + Cursor cursor = cursors[i]; + if (cursor != null) { + cursor.close(); + cursors[i] = null; + values[i] = null; + } + } + } + } catch (FetchException e) { + throw e.toPersistException(); + } + mSize = 0; + } + } +} -- cgit v1.2.3