in src/main/java/com/amazonaws/services/dynamodbv2/transactions/Transaction.java [893:972]
protected Map<String, AttributeValue> addRequest(Request callerRequest, boolean isRedrive, int numAttempts) throws DuplicateRequestException,
ItemNotLockedException, TransactionCompletedException, TransactionNotFoundException, TransactionException {
// 1. Write the full caller request to the transaction item, but not if it's being re-driven.
// (In order to re-drive, the request must already be in the transaction item)
if(! isRedrive) {
boolean success = false;
for(int i = 0; i < numAttempts; i++) {
// 1a. Verify the locks up to ensure that if we are adding a "read" request for an item that has been written to in this transaction,
// that we return the write.
verifyLocks();
try {
txItem.addRequest(callerRequest);
success = true;
break;
} catch (ConditionalCheckFailedException e) {
// The transaction is either not in PENDING anymore, or the version number incremented from another thread/process
// registering a transaction (or we started cold on an existing transaction).
txItem = new TransactionItem(txId, txManager, false);
if(State.COMMITTED.equals(txItem.getState())) {
throw new TransactionCommittedException(txId, "Attempted to add a request to a transaction that was not in state " + State.PENDING + ", state is " + txItem.getState());
} else if(State.ROLLED_BACK.equals(txItem.getState())) {
throw new TransactionRolledBackException(txId, "Attempted to add a request to a transaction that was not in state " + State.PENDING + ", state is " + txItem.getState());
} else if(! State.PENDING.equals(txItem.getState())) {
throw new UnknownCompletedTransactionException(txId, "Attempted to add a request to a transaction that was not in state " + State.PENDING + ", state is " + txItem.getState());
}
}
}
if(! success) {
throw new TransactionException(txId, "Unable to add request to transaction - too much contention for the tx record");
}
} else {
txAssert(State.PENDING.equals(txItem.getState()), txId, "Attempted to add a request to a transaction that was not in state " + State.PENDING, "state", txItem.getState());
}
// 2. Write txId to item
Map<String, AttributeValue> item = lockItem(callerRequest, true, ITEM_LOCK_ACQUIRE_ATTEMPTS);
// As long as this wasn't a duplicate read request,
// 3. Save the item image to a new item in case we need to roll back, unless:
// - it's a lock request,
// - we've already saved the item image
// - the item is transient (inserted for acquiring the lock)
saveItemImage(callerRequest, item);
// 3a. Re-read the transaction item to make sure it hasn't been rolled back or completed.
// Can be optimized if we know the transaction is already completed(
try {
txItem = new TransactionItem(txId, txManager, false);
} catch (TransactionNotFoundException e) {
releaseReadLock(callerRequest.getTableName(), callerRequest.getKey(txManager));
throw e;
}
switch (txItem.getState()) {
case COMMITTED:
doCommit();
throw new TransactionCommittedException(txId, "The transaction already committed");
case ROLLED_BACK:
doRollback();
throw new TransactionRolledBackException(txId, "The transaction already rolled back");
case PENDING:
break;
default:
throw new TransactionException(txId, "Unexpected state " + txItem.getState());
}
// 4. Apply change to item, keeping lock on the item, returning the attributes according to RETURN_VALUE
// If we are a read request, and there is an applied delete request for the same item in the tx, return null.
Map<String, AttributeValue> returnItem = applyAndKeepLock(callerRequest, item);
// 5. Optimization: Keep track of the requests that this transaction object has fully applied
if(callerRequest.getRid() != null) {
fullyAppliedRequests.add(callerRequest.getRid());
}
return returnItem;
}