From bcfaaffa3751f8c7883e41c162ba4030fd9bd21a Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Fri, 25 Oct 2013 01:36:27 +0000 Subject: Fixing ReplicatedRepository so that transactions may be entered when the master is unavailable as long as no changes are made to replicated storables. --- .../com/amazon/carbonado/layout/LayoutFactory.java | 12 +- .../repo/replicated/ReadOnlyTransaction.java | 91 ++++++++++ .../repo/replicated/ReplicatedRepository.java | 31 +++- .../repo/replicated/ReplicationTrigger.java | 190 +++++++++++++-------- .../carbonado/spi/BelatedRepositoryCreator.java | 5 +- .../carbonado/spi/BelatedStorageCreator.java | 5 +- .../carbonado/util/BelatedCreationException.java | 35 ++++ 7 files changed, 286 insertions(+), 83 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/repo/replicated/ReadOnlyTransaction.java create mode 100644 src/main/java/com/amazon/carbonado/util/BelatedCreationException.java (limited to 'src/main/java/com/amazon') 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 extends Trigger { @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 extends Trigger { @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 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); + } +} -- cgit v1.2.3