diff options
author | Jesse Morgan <jesse@jesterpm.net> | 2013-10-25 01:36:27 +0000 |
---|---|---|
committer | Jesse Morgan <jesse@jesterpm.net> | 2013-10-25 01:36:27 +0000 |
commit | bcfaaffa3751f8c7883e41c162ba4030fd9bd21a (patch) | |
tree | 7e2416a6ec9d3733a20e45e7028bf60410ac7504 | |
parent | 1a6cb41e1db610d6cea249b5b33ad65cfd945b2f (diff) |
Fixing ReplicatedRepository so that transactions may be entered when the master
is unavailable as long as no changes are made to replicated storables.
7 files changed, 286 insertions, 83 deletions
diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java index c4333da..9788739 100644 --- a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java +++ b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java @@ -148,6 +148,7 @@ public class LayoutFactory implements LayoutCapability { private Layout layout;
private FetchException fetchEx;
private PersistException persistEx;
+ private RuntimeException runtimeEx;
public synchronized void run() {
try {
@@ -157,6 +158,11 @@ public class LayoutFactory implements LayoutCapability { fetchEx = e;
} catch (PersistException e) {
persistEx = e;
+ } catch (RuntimeException e) {
+ // This is a catchall for any other exception which
+ // might happen so that it doesn't get absorbed by
+ // this thread.
+ runtimeEx = e;
}
} finally {
done = true;
@@ -178,6 +184,10 @@ public class LayoutFactory implements LayoutCapability { // Wrap to get complete stack trace.
throw new PersistException(persistEx);
}
+ if (runtimeEx != null) {
+ // Wrap to get complete stack trace.
+ throw new RuntimeException(runtimeEx);
+ }
return layout;
}
}
@@ -303,7 +313,7 @@ public class LayoutFactory implements LayoutCapability { }
throw e;
} catch (PersistException e) {
- if (e instanceof PersistDeadlockException || e instanceof PersistTimeoutException){
+ if (e instanceof PersistDeadlockException || e instanceof PersistTimeoutException) {
// Might be caused by coarse locks. Switch to nested
// transaction to share the locks.
if (top) {
diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReadOnlyTransaction.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReadOnlyTransaction.java new file mode 100644 index 0000000..1666bf4 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReadOnlyTransaction.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006-2013 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.repo.replicated; + +import java.util.concurrent.TimeUnit; + +import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.Transaction; + +/** + * ReadOnlyTransaction wraps an another transaction. It's only function is to + * serve as a marker for the ReplicatedStorage triggers that no write + * operations are allowed. + * + * @author Jesse Morgan + */ +class ReadOnlyTransaction implements Transaction { + private final Transaction mTxn; + + public ReadOnlyTransaction(Transaction txn) { + mTxn = txn; + } + + @Override + public void commit() throws PersistException { + mTxn.commit(); + } + + @Override + public void exit() throws PersistException { + mTxn.exit(); + } + + @Override + public void setForUpdate(boolean forUpdate) { + mTxn.setForUpdate(forUpdate); + } + + @Override + public boolean isForUpdate() { + return mTxn.isForUpdate(); + } + + @Override + public void setDesiredLockTimeout(int timeout, TimeUnit unit) { + mTxn.setDesiredLockTimeout(timeout, unit); + } + + @Override + public IsolationLevel getIsolationLevel() { + return mTxn.getIsolationLevel(); + } + + @Override + public void detach() { + mTxn.detach(); + } + + @Override + public void attach() { + mTxn.attach(); + } + + @Override + public boolean preCommit() throws PersistException { + return mTxn.preCommit(); + } + + @Override + public String toString() { + return "ReadOnlyTransaction wrapping { " + mTxn.toString() + " }"; + } +} + diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java index 6cf0d6c..cfe021d 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java @@ -70,6 +70,7 @@ import com.amazon.carbonado.spi.StoragePool; import com.amazon.carbonado.txn.TransactionPair;
+import com.amazon.carbonado.util.BelatedCreationException;
import com.amazon.carbonado.util.Throttle;
/**
@@ -243,18 +244,36 @@ class ReplicatedRepository }
public Transaction enterTransaction() {
- return new TransactionPair(mMasterRepository.enterTransaction(),
- mReplicaRepository.enterTransaction());
+ Transaction master;
+ try {
+ master = mMasterRepository.enterTransaction();
+ } catch (BelatedCreationException e) {
+ return new ReadOnlyTransaction(mReplicaRepository.enterTransaction());
+ }
+
+ return new TransactionPair(master, mReplicaRepository.enterTransaction());
}
public Transaction enterTransaction(IsolationLevel level) {
- return new TransactionPair(mMasterRepository.enterTransaction(level),
- mReplicaRepository.enterTransaction(level));
+ Transaction master;
+ try {
+ master = mMasterRepository.enterTransaction(level);
+ } catch (BelatedCreationException e) {
+ return new ReadOnlyTransaction(mReplicaRepository.enterTransaction(level));
+ }
+
+ return new TransactionPair(master, mReplicaRepository.enterTransaction(level));
}
public Transaction enterTopTransaction(IsolationLevel level) {
- return new TransactionPair(mMasterRepository.enterTopTransaction(level),
- mReplicaRepository.enterTopTransaction(level));
+ Transaction master;
+ try {
+ master = mMasterRepository.enterTopTransaction(level);
+ } catch (BelatedCreationException e) {
+ return new ReadOnlyTransaction(mReplicaRepository.enterTopTransaction(level));
+ }
+
+ return new TransactionPair(master, mReplicaRepository.enterTopTransaction(level));
}
public IsolationLevel getTransactionIsolationLevel() {
diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java index 4b01679..1b88e3e 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java @@ -83,99 +83,133 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> { @Override
public Object beforeInsert(S replica) throws PersistException {
- return beforeInsert(replica, false);
+ return beforeInsert(null, replica, false);
}
@Override
public Object beforeTryInsert(S replica) throws PersistException {
- return beforeInsert(replica, true);
+ return beforeInsert(null, replica, true);
}
- private Object beforeInsert(S replica, boolean forTry) throws PersistException {
- final S master = mMasterStorage.prepare();
- replica.copyAllProperties(master);
+ @Override
+ public Object beforeInsert(Transaction txn, S replica) throws PersistException {
+ return beforeInsert(txn, replica, false);
+ }
- try {
- if (forTry) {
- if (!master.tryInsert()) {
- throw abortTry();
+ @Override
+ public Object beforeTryInsert(Transaction txn, S replica) throws PersistException {
+ return beforeInsert(txn, replica, true);
+ }
+
+ private Object beforeInsert(Transaction txn, S replica, boolean forTry) throws PersistException {
+ if (txn instanceof ReadOnlyTransaction) {
+ // This operation was intended to take place in a transaction, but
+ // the master repository was unavailable when the transaction was
+ // entered.
+ throw new PersistException("Current transaction is read-only.");
+ }
+
+ final S master = mMasterStorage.prepare();
+ replica.copyAllProperties(master);
+
+ try {
+ if (forTry) {
+ if (!master.tryInsert()) {
+ throw abortTry();
+ }
+ } else {
+ master.insert();
}
- } else {
- master.insert();
+ } catch (UniqueConstraintException e) {
+ // This may be caused by an inconsistency between replica and
+ // master. Here's one scenerio: user called tryLoad and saw the
+ // entry does not exist. So instead of calling update, he/she calls
+ // insert. If the master entry exists, then there is an
+ // inconsistency. The code below checks for this specific kind of
+ // error and repairs it by inserting a record in the replica.
+
+ // Here's another scenerio: Unique constraint was caused by an
+ // inconsistency with the values of the alternate keys. User
+ // expected alternate keys to have unique values, as indicated by
+ // replica.
+
+ repair(replica);
+
+ // Throw exception since we don't know what the user's intentions
+ // really are.
+ throw e;
}
- } catch (UniqueConstraintException e) {
- // This may be caused by an inconsistency between replica and
- // master. Here's one scenerio: user called tryLoad and saw the
- // entry does not exist. So instead of calling update, he/she calls
- // insert. If the master entry exists, then there is an
- // inconsistency. The code below checks for this specific kind of
- // error and repairs it by inserting a record in the replica.
- // Here's another scenerio: Unique constraint was caused by an
- // inconsistency with the values of the alternate keys. User
- // expected alternate keys to have unique values, as indicated by
- // replica.
+ // Master may have applied sequences to unitialized primary keys, so
+ // copy primary keys to replica. Mark properties as dirty to allow
+ // primary key to be changed.
+ replica.markPropertiesDirty();
- repair(replica);
+ // Copy all properties in order to trigger constraints that
+ // master should have resolved.
+ master.copyAllProperties(replica);
- // Throw exception since we don't know what the user's intentions
- // really are.
- throw e;
+ return null;
}
- // Master may have applied sequences to unitialized primary keys, so
- // copy primary keys to replica. Mark properties as dirty to allow
- // primary key to be changed.
- replica.markPropertiesDirty();
+ @Override
+ public Object beforeUpdate(S replica) throws PersistException {
+ return beforeUpdate(null, replica, false);
+ }
- // Copy all properties in order to trigger constraints that
- // master should have resolved.
- master.copyAllProperties(replica);
+ @Override
+ public Object beforeTryUpdate(S replica) throws PersistException {
+ return beforeUpdate(null, replica, true);
+ }
- return null;
- }
+ @Override
+ public Object beforeUpdate(Transaction txn, S replica) throws PersistException {
+ return beforeUpdate(txn, replica, false);
+ }
- @Override
- public Object beforeUpdate(S replica) throws PersistException {
- return beforeUpdate(replica, false);
- }
+ @Override
+ public Object beforeTryUpdate(Transaction txn, S replica) throws PersistException {
+ return beforeUpdate(txn, replica, true);
+ }
- @Override
- public Object beforeTryUpdate(S replica) throws PersistException {
- return beforeUpdate(replica, true);
- }
+ private Object beforeUpdate(Transaction txn, S replica, boolean forTry) throws PersistException {
+ if (txn instanceof ReadOnlyTransaction) {
+ // This operation was intended to take place in a transaction, but
+ // the master repository was unavailable when the transaction was
+ // entered.
+ throw new PersistException("Current transaction is read-only.");
+ }
- private Object beforeUpdate(S replica, boolean forTry) throws PersistException {
- final S master = mMasterStorage.prepare();
- replica.copyPrimaryKeyProperties(master);
- replica.copyVersionProperty(master);
- replica.copyDirtyProperties(master);
+ final S master = mMasterStorage.prepare();
+ replica.copyPrimaryKeyProperties(master);
+ replica.copyVersionProperty(master);
+ replica.copyDirtyProperties(master);
- try {
- if (forTry) {
- if (!master.tryUpdate()) {
- // Master record does not exist. To ensure consistency,
- // delete record from replica.
- if (tryDeleteReplica(replica)) {
- // Replica was inconsistent, but caller might be in a
- // transaction and rollback the repair. Run repair
- // again in separate thread to ensure it sticks.
- repair(replica);
- }
- throw abortTry();
- }
- } else {
- try {
- master.update();
- } catch (PersistNoneException e) {
- // Master record does not exist. To ensure consistency,
- // delete record from replica.
- if (tryDeleteReplica(replica)) {
- // Replica was inconsistent, but caller might be in a
- // transaction and rollback the repair. Run repair
- // again in separate thread to ensure it sticks.
- repair(replica);
+ try {
+ if (forTry) {
+ if (!master.tryUpdate()) {
+ // Master record does not exist. To ensure consistency,
+ // delete record from replica.
+ if (tryDeleteReplica(replica)) {
+ // Replica was inconsistent, but caller might be in a
+ // transaction and rollback the repair. Run repair
+ // again in separate thread to ensure it sticks.
+ repair(replica);
+ }
+ throw abortTry();
}
+ } else {
+ try {
+ master.update();
+ } catch (PersistNoneException e) {
+ // Master record does not exist. To ensure consistency,
+ // delete record from replica.
+ if (tryDeleteReplica(replica)) {
+ // Replica was inconsistent, but caller might be in a
+ // transaction and rollback the repair. Run repair
+ // again in separate thread to ensure it sticks.
+ repair(replica);
+ }
throw e;
}
}
@@ -199,6 +233,18 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> { @Override
public Object beforeDelete(S replica) throws PersistException {
+ return beforeDelete(null, replica);
+ }
+
+ @Override
+ public Object beforeDelete(Transaction txn, S replica) throws PersistException {
+ if (txn instanceof ReadOnlyTransaction) {
+ // This operation was intended to take place in a transaction, but
+ // the master repository was unavailable when the transaction was
+ // entered.
+ throw new PersistException("Current transaction is read-only.");
+ }
+
S master = mMasterStorage.prepare();
replica.copyPrimaryKeyProperties(master);
diff --git a/src/main/java/com/amazon/carbonado/spi/BelatedRepositoryCreator.java b/src/main/java/com/amazon/carbonado/spi/BelatedRepositoryCreator.java index e004340..30fff0c 100644 --- a/src/main/java/com/amazon/carbonado/spi/BelatedRepositoryCreator.java +++ b/src/main/java/com/amazon/carbonado/spi/BelatedRepositoryCreator.java @@ -33,6 +33,7 @@ import com.amazon.carbonado.Transaction; import com.amazon.carbonado.capability.Capability;
+import com.amazon.carbonado.util.BelatedCreationException;
import com.amazon.carbonado.util.BelatedCreator;
/**
@@ -158,8 +159,8 @@ public class BelatedRepositoryCreator extends BelatedCreator<Repository, Support return false;
}
- private IllegalStateException error() {
- return new IllegalStateException
+ private BelatedCreationException error() {
+ return new BelatedCreationException
("Creation of Repository \"" + mBuilder.getName() + "\" is delayed");
}
}
diff --git a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java index 9a66b29..5f5b716 100644 --- a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java +++ b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java @@ -29,6 +29,7 @@ import com.amazon.carbonado.SupportException; import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.util.BelatedCreationException;
import com.amazon.carbonado.util.BelatedCreator;
/**
@@ -135,8 +136,8 @@ public class BelatedStorageCreator<S extends Storable> throw error();
}
- private IllegalStateException error() {
- return new IllegalStateException
+ private BelatedCreationException error() {
+ return new BelatedCreationException
("Creation of Storage for type \"" + mStorableType.getName() + "\" is delayed");
}
}
diff --git a/src/main/java/com/amazon/carbonado/util/BelatedCreationException.java b/src/main/java/com/amazon/carbonado/util/BelatedCreationException.java new file mode 100644 index 0000000..ea27e20 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/BelatedCreationException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 Amazon.com Inc. All Rights Reserved. + * AMAZON.COM CONFIDENTIAL + */ + +package com.amazon.carbonado.util; + +/** + * Exception throw when attempting to perform an unavailable operation on an + * object undergoing belated creation. + * + * @see com.amazon.carbonado.util.BelatedCreator; + * + * @author Jesse Morgan (morganjm) + */ +public class BelatedCreationException extends IllegalStateException { + /** + * Create a new exception with the given message. + * + * @param message The exception message. + */ + public BelatedCreationException(String message) { + super(message); + } + + /** + * Create a new exception with the given message and cause. + * + * @param message The exception message. + * @param cause The cause of the exception. + */ + public BelatedCreationException(String message, Throwable cause) { + super(message, cause); + } +} |