Next: , Previous: Multi-threaded Applications, Up: User Guide


4.11 Transaction Details

Transactions are dynamic contexts in which all side effects to persistent slots and other persistent objects such as BTrees are guaranteed to have the ACID properties: atomicity, consistency, isolation and durability. On a normal exit from context, the side effects are committed as a group. On a non-local exit, the transaction is aborted.

For most users, the tutorial section Using Transactions is the best introduction to transactions. This section adds to that by exposing some of the details of how it is implemented.

To reiterate, there are a few important restrictions to adhere to:

4.11.1 with-transaction internals

The with-transaction macro wraps the body expression with an anonymous lambda expression. This closure is passed to a call to the execute-transaction generic function which is specialized to the current data store.

The only bookkeeping done by the macro is ensuring that the :parent argument is checked for the current dynamic transaction context. If it is not owned by the default or provided store controller, then it is not passed to execute-transaction. This maintains a continuous dynamic stack transactions through the with/ensure transaction macros, but allows for a single leaf transaction to another store controller.

Be very careful about mixing transactions between store controller. This facility was only added to ensure that migrate worked correctly.

The macro processes keywords arguments :store-controller (defaults to *store-controller*), :parent (defaults to *current-transaction*) and :retries and passes the remaining keywords to the call to execute-transaction allowing the user to pass data store specific transaction keywords to their preferred data store. The consumed keywords are analyzed and then passed on to execute-transaction.

Any non-standard keywords for a given data store will be ignored by other data store implementation of execute-transaction so portable programs should not use keywords that change the semantics of the transaction.

ensure-transaction only calls execute-transaction if it needs to create a fresh transaction. If the transaction in *current-transaction* exists and belongs to the store controller passed to ensure-transaction then it merely calls the transaction closure, relying on the environment that created the transaction to handle any exit procedures and determining whether to abort or commit.

*current-transaction* contains transaction records during the dynamic execution of a transaction. These records capture any data store specific bookkeeping as well as the store-controller that the transaction is associated with.

4.11.2 execute-transaction internals

See the Elephant Architecture section for details on how execute-transaction works. It will provide some deeper insight into the transaction system.

4.11.3 Building your own transactional framework

Data stores are required to implement three primitive transaction methods: controller-start-transaction, controller-abort-transaction and controller-commit-transaction. These are wrappers for the data store's primitive transaction mechanism. If you use these, it is up to you to make sure that you properly manage nested transactions, maintain the state of *current-transaction* handle any automated retries you might want, and handle detecting

If you use these, you are on your own - it is easy to make mistakes with transactions and create very complex bugs that are hard to track down. Most users are much better off sticking with the two transaction macros and the underlying execute-transaction method.

4.11.4 Analyzing Dynamic Transaction Behavior

You can trace elephant::execute-transaction to see the sequence of calls that occur dynamically and detect where and how many transactions are and are not happening.