-
Notifications
You must be signed in to change notification settings - Fork 1
Working with transactions
Transactions are represented by an instance of the CTKLockingTransaction class (analogous to Clojure’s LockingTransaction class)
Transactions are created and managed by the framework, so you should not need to create transactions directly.
You typically get a transaction object by using the class method transaction
CTKLockingTransaction *txn = [CTKLockingTransaction transaction];
This will return the transaction object associated with the current thread. CTKLockingTransaction will allocate and instantiate an instance for the current thread if one does no exist. Also since this is not an object managed by you, you should not worry about a deallocation, this will be handled by the class as well.
The API supports an object oriented style but as the STM operates using an optimistic locking approach your transaction might require to be retried. An OO style forces you to explicitly perform the retry by using a loop.
CTKReference *mapRef; // assuming we've been passed the ref pointing to a map
CTKLockingTransaction *txn = [CTKLockingTransaction transaction];
NSError *error = nil;
NSException *savedException = nil;
BOOL done = NO;
for(int i = 0; !done && i < txn.retryLimit; i++)
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
@try {
// We need to initialise the transaction
[txn begin];
// Then we need to obtain the latest committed value for the reference
// of the object we want to modify
CTKPersistentHashMap *map = (CTKPersistentHashMap *) [mapRef dereference];
// Then we perform the changes to our map
map = [map mapBySettingObject:@"Alejandro" forKey:@"Name"];
map = [map mapBySettingObject:@"Buenos Aires" forKey:@"City of Birth"];
map = [map mapByRemovingObjectForKey:@"Birthdate"];
// Finally we assign the new map to the reference
[mapRef setValue:map];
// We won't get the error since we are interested in retrying, so we pass nil
done = [txn commit:nil];
}
@catch (CTKTransactionRetryException *re) {
// do nothing so that we can retry
}
@catch (NSException *e)
{
savedException = [e retain];
@throw savedException;
}
@finally {
[pool drain];
[savedException autorelease];
}
}
The functional style uses Objective-C Blocks, and it allows you to focus on the actual operation you want to perform and living the framework to do the rest
CTKReference *mapRef; // assuming we've been passed the ref pointing to a map
CTKLockingTransaction *txn = [CTKLockingTransaction transaction];
NSError *error = nil;
id (^myOperation)(void) = ^ id (void) {
// First we need to obtain the latest committed value for the reference
// of the object we want to modify
CTKPersistentHashMap *map = (CTKPersistentHashMap *) [mapRef dereference];
// Then we perform the changes to our map
map = [map mapBySettingObject:@"Alejandro" forKey:@"Name"];
map = [map mapBySettingObject:@"Buenos Aires" forKey:@"City of Birth"];
map = [map mapByRemovingObjectForKey:@"Birthdate"];
// Finally we assign the new map to the reference
[mapRef setValue:map];
// Returning a value is just a convinience, you could just return nil
return map;
};
// This method will retry up until txn.retryLimit is reached
id result = [txn performBlock:myOperation error:&error];
if (result == nil && error != nil)
{
NSLog(@"Transaction failed after retry");
}
Using the functional style I do not need to handle the CTKTransactionRetryException since it is done by the performBlock:error: method. We should handle any other exceptions though.