protected void rollbackItemAndReleaseLock()

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);
        }
    }