diff options
5 files changed, 114 insertions, 39 deletions
diff --git a/src/main/java/com/amazon/carbonado/Transaction.java b/src/main/java/com/amazon/carbonado/Transaction.java index 5c8203f..1c2c06d 100644 --- a/src/main/java/com/amazon/carbonado/Transaction.java +++ b/src/main/java/com/amazon/carbonado/Transaction.java @@ -117,4 +117,30 @@ public interface Transaction { * Returns the isolation level of this transaction.
*/
IsolationLevel getIsolationLevel();
+
+ /**
+ * Detaches this transaction from the current thread. It can be attached
+ * later, and to any thread which currently has no thread-local
+ * transaction.
+ *
+ * <p>Detaching a transaction also detaches any parent and nested child
+ * transactions. Attaching any of them achieves the same result as
+ * attaching this transaction.
+ *
+ * @throws IllegalStateException if transaction is attached to a different
+ * thread
+ * @since 1.2
+ */
+ void detach();
+
+ /**
+ * Attaches this transaction to the current thread, if it has been
+ * detached. Attaching a transaction also attaches any parent and nested
+ * child transactions.
+ *
+ * @throws IllegalStateException if current thread has a different
+ * transaction already attached
+ * @since 1.2
+ */
+ void attach();
}
diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java index fd88f35..dd2a4f5 100644 --- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java +++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java @@ -83,6 +83,20 @@ class LoggingTransaction implements Transaction { return mTxn.getIsolationLevel();
}
+ public void detach() {
+ if (mLog.isEnabled()) {
+ mLog.write("Transaction.detach() on " + idChain());
+ }
+ mTxn.detach();
+ }
+
+ public void attach() {
+ if (mLog.isEnabled()) {
+ mLog.write("Transaction.attach() on " + idChain());
+ }
+ mTxn.attach();
+ }
+
private String idChain() {
if (mParent == null) {
return String.valueOf(mID);
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java index 4a28d24..a2b1375 100644 --- a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java +++ b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java @@ -74,32 +74,36 @@ public abstract class TransactionManager<Txn> { * which does not currently have a TransactionScope.
*
* @return detached thread-local TransactionScope or null if none
+ * @since 1.2
*/
- /*
public TransactionScope<Txn> detachLocalScope() {
TransactionScope<Txn> scope = mLocalScope.get();
if (scope != null) {
- if (!scope.detach()) {
- scope = null;
- } else {
- mLocalScope.remove();
- }
+ scope.markDetached();
+ mLocalScope.remove();
}
return scope;
}
- */
// Called by TransactionScope.
- /*
- void attach(TransactionScope<Txn> scope) {
+ boolean removeLocalScope(TransactionScope<Txn> scope) {
+ TransactionScope<Txn> existing = mLocalScope.get();
+ if (existing == null || existing == scope) {
+ mLocalScope.remove();
+ return true;
+ }
+ return false;
+ }
+
+ // Called by TransactionScope.
+ boolean setLocalScope(TransactionScope<Txn> scope) {
TransactionScope<Txn> existing = mLocalScope.get();
if (existing == null || existing == scope) {
mLocalScope.set(scope);
- } else {
- throw new IllegalStateException("Current thread already has a transaction scope");
+ return true;
}
+ return false;
}
- */
/**
* Closes all transaction scopes. Should be called only when repository is
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java b/src/main/java/com/amazon/carbonado/spi/TransactionPair.java index d97aef4..ff07bc2 100644 --- a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java +++ b/src/main/java/com/amazon/carbonado/spi/TransactionPair.java @@ -86,4 +86,24 @@ public class TransactionPair implements Transaction { return mPrimaryTransaction.getIsolationLevel()
.lowestCommon(mSecondaryTransaction.getIsolationLevel());
}
+
+ public void detach() {
+ mPrimaryTransaction.detach();
+ try {
+ mSecondaryTransaction.detach();
+ } catch (IllegalStateException e) {
+ mPrimaryTransaction.attach();
+ throw e;
+ }
+ }
+
+ public void attach() {
+ mPrimaryTransaction.attach();
+ try {
+ mSecondaryTransaction.attach();
+ } catch (IllegalStateException e) {
+ mPrimaryTransaction.detach();
+ throw e;
+ }
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java index 0ac2cee..82a20eb 100644 --- a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java +++ b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java @@ -41,8 +41,6 @@ import com.amazon.carbonado.Transaction; * @see TransactionManager
*/
public class TransactionScope<Txn> {
- private static final int OPEN = 0, DETACHED = 1, CLOSED = 2;
-
final TransactionManager<Txn> mTxnMgr;
final Lock mLock;
@@ -51,12 +49,12 @@ public class TransactionScope<Txn> { // Tracks all registered cursors by storage type.
private Map<Class<?>, CursorList<TransactionImpl<Txn>>> mCursors;
- private int mState;
+ private boolean mClosed;
+ private boolean mDetached;
TransactionScope(TransactionManager<Txn> txnMgr, boolean closed) {
mTxnMgr = txnMgr;
mLock = new ReentrantLock(true);
- mState = closed ? CLOSED : OPEN;
}
/**
@@ -192,7 +190,7 @@ public class TransactionScope<Txn> { public boolean isForUpdate() {
mLock.lock();
try {
- return (mState == CLOSED || mActive == null) ? false : mActive.isForUpdate();
+ return (mClosed || mActive == null) ? false : mActive.isForUpdate();
} finally {
mLock.unlock();
}
@@ -205,7 +203,7 @@ public class TransactionScope<Txn> { public IsolationLevel getIsolationLevel() {
mLock.lock();
try {
- return (mState == CLOSED || mActive == null) ? null : mActive.getIsolationLevel();
+ return (mClosed || mActive == null) ? null : mActive.getIsolationLevel();
} finally {
mLock.unlock();
}
@@ -215,41 +213,46 @@ public class TransactionScope<Txn> { * Attach this scope to the current thread, if it has been {@link
* TransactionManager#detachLocalScope detached}.
*
- * @throws IllegalStateException if scope is not detached or if current
- * thread already has a transaction scope
+ * @throws IllegalStateException if current thread has a different
+ * transaction already attached
*/
- /*
public void attach() {
mLock.lock();
try {
- if (mState != DETACHED) {
- throw new IllegalStateException("Transaction scope is not detached");
+ if (mTxnMgr.setLocalScope(this)) {
+ mDetached = false;
+ } else {
+ throw new IllegalStateException
+ ("Current thread has a different transaction already attached");
}
- mState = OPEN;
} finally {
mLock.unlock();
}
- mTxnMgr.attach(this);
}
- */
- /**
- * @return false if closed
- */
- /*
- boolean detach() {
+ // Called by TransactionImpl.
+ void detach() {
mLock.lock();
try {
- if (mState == CLOSED) {
- return false;
+ if (mTxnMgr.removeLocalScope(this)) {
+ mDetached = true;
+ } else {
+ throw new IllegalStateException("Transaction is attached to a different thread");
}
- mState = DETACHED;
- return true;
} finally {
mLock.unlock();
}
}
- */
+
+ // Called by TransactionManager.
+ void markDetached() {
+ mLock.lock();
+ try {
+ mDetached = true;
+ } finally {
+ mLock.unlock();
+ }
+ }
/**
* Exits all transactions and closes all cursors. Should be called only
@@ -258,7 +261,7 @@ public class TransactionScope<Txn> { void close() throws RepositoryException {
mLock.lock();
try {
- if (mState != CLOSED) {
+ if (!mClosed) {
while (mActive != null) {
mActive.exit();
}
@@ -269,7 +272,7 @@ public class TransactionScope<Txn> { }
}
} finally {
- mState = CLOSED;
+ mClosed = true;
mLock.unlock();
}
}
@@ -278,7 +281,7 @@ public class TransactionScope<Txn> { * Caller must hold mLock.
*/
private void checkClosed() {
- if (mState == CLOSED) {
+ if (mClosed) {
throw new IllegalStateException("Repository is closed");
}
}
@@ -409,6 +412,14 @@ public class TransactionScope<Txn> { return mLevel;
}
+ public void detach() {
+ mScope.detach();
+ }
+
+ public void attach() {
+ mScope.attach();
+ }
+
// Caller must hold mLock.
<S extends Storable> void register(Cursor<S> cursor) {
if (mCursorList == null) {
|