BNZTransaction



Abstract

a transaction collects a set of BNZAtomicChange objects that can be applied in an ACID way

Discussion

A BNZTransaction collects a set of atomic changes that later are applied in an "all of them or none of them" fashion. A transaction implements a state machine (see the BNZTransactionStatus enum documentation for details); it always starts ACTIVE. During this phase, atomic changes are collected. When a transaction is committed (by calling commit), the changes are applied one after another. If one goes wrong, the whole transaction is rolled back, which means, the changes are undone.

Every transaction is initially associated with a NSThread and it can only be committed or rolled back within the context of this thread.

Usually, objects that participate in the BNZTransactionalNotificationCenter framework will use the current transaction to create changes and to find changes that have been done within their own thread to decide which values to return in getters. this way, a change in a thread is visible even before the transaction is committed but they are hidden outside the thread until committed.

Example:
//getter of a transactional object
- (NSString*)firstName {
//retrieve transaction of current thread
BNZTransaction transaction = [[BNZTransactionalNotificationCenter defaultCenter] currentTransaction];
//retrieve the last change of the same type that has already been done
id change = [transaction lastChangeOfType:"PersonFirstNameChange" onTarget:self]; //the object that is returned here depends on the setFirstName implementation!
//if there was such a change, pretend to have the new value
if (change != nil) {
return [change newFirstName];
}
//no such change in current thread, return field value
return myFirstName;
}


Note: there are static helper methods in BNZTransactionalNotificationCenter as well as in BNZAtomicPropertyChange that allow the implementation of the setters and getters in a transactional object to be much easier, maybe even only one line

This will result in a behavior like this:
- (void)sameThread {
Person p = [[Person alloc] initWithfirstName:"Alice"]; //p is named Alice now


//do not immediately commit each change [[[BNZTransactionalNotificationCenter defaultCenter] currentTransaction] turnOffAutoCommit];

//change the name to something new [p setName:"Bob"];

//change is visible, as expected NSLog("Person is called %(a) in same thread", [person firstName]); //fn will be "Bob" even without commit

//change is not visible in another thread [NSThread detachNewthreadSelector:(a)selector(otherThread:) toTarget:self withObject:p]; //will still yield "Alice"

//commit changes [transaction commit];

//change is now also visible in another thread [NSThread detachNewthreadSelector:(a)selector(otherThread:) toTarget:self withObject:p]; //will yield "Bob" }

- (void)otherThread:(Person*)person { //should create autoreleasepool:) NSLog((a)"Person is called %(a) in other thread", [person firstName]); }




Methods

addAtomicChange:

Abstract: add an atomic change to the transaction
-  addAtomicChange:(id)change; 

adds an atomic change to the transaction if autoCommit is on, this will immediately result in a commit

may throw a kNotAnActiveTransactionException exception if not in ACTIVE or MARKED_ROLLBACK state

Parameters

NameDescription
changean atomic change
Result: returns self

addTransactionToWaitFor:

Abstract: add a transaction that has to be committed before this transaction
-  addTransactionToWaitFor:(BNZTransaction*)transaction; 

Parameters

NameDescription
transactiona transaction this transaction should wait for
Result: reeturns self

changes

Abstract: all changes in this transaction
- (NSArray*)changes; 

currently, all changes are stored in the order in which they were added. On a commit, all changes are applied in the same order. So if you first set a property, say, to "foo" and then to "bar", the commit will result in first setting it to "foo" and then to "bar" even though the first change may be ommitted.

However, this may change in future releases so that only one change of the same type on the same target can be part of the transaction. Then, the AtomicChange implementation may need to care for addivitve changes themselves (like adding objects to a collection one at a time => each add would then find its predecessing change and include its addings to the own addings). But as mentioned, this is an idea for a future release and does not work this way currently.

Result: an array of all changes in this transaction

changesOfType:

Abstract: return all changes on the given target of one of the given types in the change set
- (NSArray*)changesOfTypes:(NSArray*)types onTarget:(id)target; 

this method allows to filter the change set for changes of certain types. this allows, for example, BNZAtomicChange implementations to find corresponding changes such as "addObject" and "removeObject" changes for arrays

Parameters

NameDescription
typesan array of changeTypes (any objects, most often Strings, however) that should be included in the result
targetthe target object for the changes. if nil, all changes of the given types are returned
Result: an array containing changes of one of the given types. Order of changes is preserved

changesforObserver:

Abstract: finds all changes in the change set, for which the given observer registered in the BNZTransactionalNotificationCenter
- (NSArray*)changesForObserver:(id)observer inCallback:(SEL)callback; 

Within a transaction, several (unrelated) atomic changes can be included. Before and after commit all objects are called that registered for at least one change (of a certain type and/or on a certain object). These objects also can inspect the changes in the transaction they did not register for.

However, if they are only interested in changes they originally registered for in a certain callback, this method returns filters the change set to return only those.

This method can be used to filter out the real changes of interest.

e.g.: registered selector "handleChangesMethod:" as hasCommittedCallback for "FooChange" and "BarChange" changes. in "handleChangesMethod:" one can then call

NSArray* changes = [transaction changesForObserver:self inCallback:

Parameters

NameDescription
observeran object that registered for receiving change notifications
callbacka selector for which the observer registered.
Result: an array containing all changes in the transaction change set for wich the registrations apply

commit

Abstract: commit the changes
- (BOOL)commit; 

commit all the changes of this transaction. will inform transaction observers about the upcoming commit, commit all changes, and inform them again when committed.

Before a commit, observers can prevent the whole commit by returning NO or throwing an exception in their callback.

this method returns YES if the commit was successfull and NO if the transaction was rolled back.

will call rollback for sure if transaction is in state MARKED_ROLLBACK.

may throw a kNotAnActiveTransactionException exception if not in ACTIVE or PREPARED state

may throw a kNoTransactionForCurrentThreadException if the transaction thread is not equal to the caller thread.

might call prepare if not already in PREPARED state

will call clearToCommit of BNZTransactionalNotificationCenter. This method blocks the current thread until all currently committing transactions that have an intersecting change set have finished.

see hasIntersectingChangeSet: for details

Result: a BOOL indicating whether the commit was successful (YES) or whether the transaction has been rolled back (NO)

commitException

Abstract: return the exception that has interrupted the commit
- (NSException*)commitException; 

if a commit has been interrupted by an exception either within the notification phase of a transaction observer or within the commit by an AtomicChange, this will return the exception (commit will have returned NO, then).

Result: the exception that interrupted the commit, if any. nil if no exception was thrown.

hasIntersectingChangeSet:

Abstract: check, whether the change sets of the two transactions intersect
- (BOOL)hasIntersectingChangeSet:(BNZTransaction*)transaction; 

change sets intersect, if there exist at least one change object per transaction that have the same type and the same target.

If this is the case, the commit of the transactions can not be intermingled; instead, the earlier transaction has to finish commit (or rollback) before the later transaction can start.

This is done to be sure that each change set is committed as a whole before a conflicting change set is committed. Imagine two transactions: init: p.firstName ="Carter", p.lastName="Dole", address.street="4th Ave" A changes property p.firstName to "Bob" and address.street to "5th Ave" B changes p.firstName to "Alice" and p.lastName to "Johnson"

The transactions intersect because both change p.firstName. If they would be committed in parallel, it may happen, that a third thread reads p.firstName "Bob" p.lastName="Johnson", address.street="4th Ave" which is an intermingled state of A (first name, street) and B (last name).

When executing one after another, possible states are "Carter", "Dole", "4th Ave" (initial) "Bob", "Dole", "5th Ave" (after Transaction A) "Alice", "Johnson", "5th Ave" (after Transaction B)

In this case: The order of Transaction A and B is not defined. So even though it is somehow random if the person will be "Bob" or "Alice" afterwards (depending on which transaction commits first), the states themselfe are at least consistent.

In short: the change of the later transaction wins.

Note: there may be other strategies that could be incorporated later. Such as: the earlier transaction is committed, the later fails.


initWithThread:

Abstract: construct a new transaction that is associated with the given thread
- (id)initWithThread:(NSThread*)thread; 

BNZTransaction objects shall not be created directly. Instead, [BNZTransactionalNotificationCenter currentTransaction] will create a transaction for the current thread if there is none.

Parameters

NameDescription
threadthe thread this transaction is associated with
Result: a transaction object

isAutoCommit

- (BOOL)isAutoCommit; 

initially, every transaction is set to autocommit YES this means, that a call to addAtomicChange: immediately results in a commit (i.e., every atomic change is committed on its own)

Result: the current value of autoCommit

lastChangeOfType:onTarget

Abstract: return the change of the given type on the given target that has been added at last
- (id)lastChangeOfType:(id)type onTarget:(id)target; 

return the change of the given type on the given target that has been added at last. Mainly used, if changes of the same type overwrite each other and only the last change of that type therefore counts. e.g., PersonFirstNameChange: only the last setFirstName: is important, so in the getter firstName, the object can simply ask for the last change of the PersonFirstNameChange

Parameters

NameDescription
typethe type of the change that should be returned
targetthe target of the change that should be returned
Result: the BNZAtomicChange object of the given type on the given target that has been added latest, if any. nil, if no such change was found

numberOfTransactionsToWaitFor

Abstract: the numbers of transactions contained in transactionsToWaitFor list
- (int)numberOfTransactionsToWaitFor; 

Result: the number of transactions in transactionsToWaitFor list

prepare

Abstract: prepare for commit or rollback
-  prepare; 

this method prepares the transaction for a commit or a rollback. It is called automatically when needed; however, a caller may want to prepare the transaction manually (long) before the commit. After the transaction is prepared, no additional changes can be added to the transaction.

Preparing the transaction currently only fills the observations array with BNZObservation objects that registered for being notified of one or more of the changes contained in this transaction.

may throw a kNotAnActiveTransactionException exception if not in ACTIVE or MARKED_ROLLBACK state

Result: returns self

removeTransactionToWaitFor:

Abstract: removes a transaction from transactionsToWaitFor list
-  removeTransactionToWaitFor:(BNZTransaction*)transaction; 

Parameters

NameDescription
transactionthe transaction to remove
Result: returns self

rollback

Abstract: roll back the transaction
- (void)rollback; 

rolls back the transaction. Iterates all atomic changes in the change set and calls rollback.

may throw a kNoTransactionForCurrentThreadException if the transaction thread is not equal to the caller thread.

may throw a kNotAnActiveTransactionException exception if not in ACTIVE, PREPARED, COMMITTING, or MARKED_ROLLBACK state

will call prepare if not already done


setRollbackOnly

Abstract: marks the transaction so that only possible outcome is a rollback
-  setRollbackOnly; 

when calling this method, the state of the transaction becomes MARKED_ROLLBACK. The transaction can be used as an active one, however, the only possible outcome of calling commit is a rollback.

This is helpful if you want to invalidate a transaction (e.g., you discovered an erroneous state) without affecting the code that comes later until the commit.

e.g.:

- (void)do {
BNZTransaction transaction = ...; //transaction with no auto commit
[foo setBar:"bar"]; //clearly works


\\illegal state discovered, may even be inside some other method if (cond) { [transaction setRollbackOnly]; }

[bar setFoo:"foo"]; //still works, even though it is clear that transaction will be rolled back [transaction commit]; //will roll back!! }


may throw a kNotAnActiveTransactionException exception if not in ACTIVE or MARKED_ROLLBACK state

Result: returns self

status

Abstract: retrieve the state of the transaction.
- (enum BNZTransactionStatus)status; 

see the documentation for the BNZTransactionStatus enum for details

Result: the current status

thread

Abstract: retreive the thread this transaction is associated with
- (NSThread*)thread; 

Result: the thread

threadLock

Abstract: retreive a lock for halting the thread during a commit
- (NSConditionLock*)threadLock; 

the commit of a transaction may be halted until previous transactions are finished. This lock allows the BNZTransactionalNotification center to do so. see the [BNZTransactionalNotification clearToCommit] for details

Result: an NSConditionLock that can be used exclusively for this transaction

turnOffAutoCommit

Abstract: turn of the auto commit feature
-  turnOffAutoCommit; 

calling this method will turn off the autoCommit behavior Note that once this method is called, there is no way to turn autoCommit on again, because this does not make sense. (after committing or rolling back, however, the next transaction will have autoCommit on again).

may throw a kNotAnActiveTransactionException exception if not in ACTIVE or MARKED_ROLLBACK state

Result: returns self

waitsForTransaction:

Abstract: is the given transaction in the transactionsToWaitFor list
- (BOOL)waitsForTransaction:(BNZTransaction*)transaction; 

Parameters

NameDescription
transactionthe transaction in question
Result: a BOOL indicating wheather the given transaction is in the transactionsToWaitFor list

(Last Updated 8/31/2006)