in src/main/java/com/amazonaws/services/dynamodbv2/transactions/Transaction.java [700:785]
protected void rollbackItemAndReleaseLock(String tableName, Map<String, AttributeValue> key, Boolean isGet, Integer rid) {
// TODO there seems to be a race that leads to orphaned old item images (but is still correct in terms of the transaction)
// A previous master could have stalled after writing the tx record, fall asleep, and then finally insert the old item image
// after this delete attempt goes through, and then the sleepy master crashes. There's no great way around this,
// so a sweeper needs to deal with it.
// Possible outcomes:
// 1) We know for sure from just the request (getItem) that we never back up the item. Release the lock (and delete if transient)
// 2) We found a backup. Apply the backup.
// 3) We didn't find a backup. Try deleting the item with expected: 1) Transient, 2) Locked by us, return success
// 4) Read the item. If we don't have the lock anymore, meaning it was already rolled back. Return.
// 5) We failed to take the backup, but should have.
// a) If we've applied, assert.
// b) Otherwise release the lock (okay to delete if transient to re-use logic)
// 1. Read locks don't have a saved item image, so just unlock them and return
if(isGet != null && isGet) {
releaseReadLock(tableName, key);
return;
}
// Read the old item image, if the rid is known. Otherwise we treat it as if we don't have an item image.
Map<String, AttributeValue> itemImage = null;
if(rid != null) {
itemImage = txItem.loadItemImage(rid);
}
if(itemImage != null) {
// 2. Found a backup. Replace the current item with the pre-changes version of the item, at the same time removing the lock attributes
txAssert(itemImage.remove(AttributeName.TRANSIENT.toString()) == null, txId, "Didn't expect to have saved an item image for a transient item", "itemImage", itemImage);
itemImage.remove(AttributeName.TXID.toString());
itemImage.remove(AttributeName.DATE.toString());
txAssert(! itemImage.containsKey(AttributeName.APPLIED.toString()), txId, "Old item image should not have contained the attribute " + AttributeName.APPLIED.toString(), "itemImage", itemImage);
try {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>();
expected.put(AttributeName.TXID.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(txId)));
PutItemRequest put = new PutItemRequest()
.withTableName(tableName)
.withItem(itemImage)
.withExpected(expected);
txManager.getClient().putItem(put);
} catch (ConditionalCheckFailedException e) {
// Only conditioning on "locked by us", so if that fails, it means it already happened (and may have advanced forward)
}
} else {
// 3) We didn't find a backup. Try deleting the item with expected: 1) Transient, 2) Locked by us, return success
try {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>();
expected.put(AttributeName.TXID.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(txId)));
expected.put(AttributeName.TRANSIENT.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(BOOLEAN_TRUE_ATTR_VAL)));
DeleteItemRequest delete = new DeleteItemRequest()
.withTableName(tableName)
.withKey(key)
.withExpected(expected);
txManager.getClient().deleteItem(delete);
return;
} catch (ConditionalCheckFailedException e) {
// This means it already happened (and may have advanced forward)
// Technically there could be a bug if it is locked by us but not marked as transient.
}
// 4) Read the item. If we don't have the lock anymore, meaning it was already rolled back. Return.
// 5) We failed to take the backup, but should have.
// a) If we've applied, assert.
// b) Otherwise release the lock (okay to delete if transient to re-use logic)
// 4) Read the item. If we don't have the lock anymore, meaning it was already rolled back. Return.
Map<String, AttributeValue> item = getItem(tableName, key);
if(item == null || ! txId.equals(getOwner(item))) {
// 3a) We don't have the lock anymore. Return.
return;
}
// 5) We failed to take the backup, but should have.
// a) If we've applied, assert.
txAssert(! item.containsKey(AttributeName.APPLIED.toString()), txId, "Applied change to item but didn't save a backup", "table", tableName, "key", key, "item" + item);
// b) Otherwise release the lock (okay to delete if transient to re-use logic)
releaseReadLock(tableName, key);
}
}