picotm  0.2.0
Files
The Module Interface

Picotm is extensible. This section explains the module interface and how to implement your own module on top. More...

Collaboration diagram for The Module Interface:

Files

file  compiler.h
 Contains macros for dealing with compiler extensions.
 
file  picotm-error.h
 Contains struct picotm_error and helper functions.
 
file  picotm-module.h
 Picotm's module interface.
 

Detailed Description

Picotm is only a transaction manager. It maintains transactions but doesn't provide their functionality. All actual transactional operations are provided by additional modules. Picotm can be extended with arbitrary functionality. The interface is available to external libraries, so no modification to picotm is required.

Registering Your Module

To register a module, call picotm_register_module(). The call receives a number of module-specific call-back functions and module data, and returns a unique module number. The call-back functions are invoked by picotm to instruct the module during different phases of a transaction. The returned module number is later required when inserting events into the transaction log.

For an example, let's look at a transactional allocator. Registering the module might look like this.

long res = picotm_register_module(NULL,
NULL,
NULL,
NULL,
NULL,
apply, // See below.
undo, // See below.
NULL,
NULL,
NULL,
NULL);
if (res < 0) {
// abort with an error
}
unsigned int module_number = res;

Modules are registered per-thread. You have to register your module on each thread where it is used.

Injecting Events into the Transaction Log.

When the user invokes an operation of your module, the module might want to store an event in the transaction log. This is done by invoking picotm_inject_event(). During the transaction's commit, events in the transaction log are applied in the order they appear in the log. During a roll-back, events are reverted in opposite order.

In the case of our transactional allocator, we have two functions, malloc() and free().

enum {
CMD_MALLOC,
CMD_FREE
};
// Public interface called from with transaction.
void*
malloc_tx(size_t siz)
{
void* ptr = malloc(siz);
if (!ptr) {
// In real-world code, do error handling here!
return NULL;
}
int res = picotm_inject_event(module_number, CMD_MALLOC, ptr);
if (res < 0) {
// In real-world code, do error handling here!
return NULL;
}
return ptr;
}
// Public interface called from with transaction.
void
free_tx(void* ptr)
{
int res = picotm_inject_event(module_number, CMD_FREE, ptr);
if (res < 0) {
// In real-world code, do error handling here!
return NULL;
}
return ptr;
}

Applying and Reverting Events

For applying or reverting events, your module must provide the respective callbacks to picotm_register_module(). These are invoked by picotm during the life time of a transaction.

For our example of the transactional allocator, these call-back functions will look like this.

int
apply(const struct picotm_event* event, size_t nevents, void* data, picotm_error* error)
{
while (nevents) {
if (event->call == CMD_MALLOC) {
// Nothing to do.
}
if (event->call == CMD_FREE) {
// Apply free().
free((void*)event->cookie);
}
++event;
--nevents;
}
return 0;
}
int
undo(const struct picotm_event* event, size_t nevents, void* data, picotm_error* error)
{
events += nevents;
while (nevents) {
--event;
if (event->call == CMD_MALLOC) {
// Revert malloc().
free((void*)event->cookie);
}
if (event->call == CMD_FREE) {
// Nothing to do.
}
--nevents;
}
return 0;
}

In our example we have registered a module for a transactional allocator. The malloc operation allocates memory, but reverts this allocation during an undo. The free operation doesn't actually free the memory immediately, be creates an event to free the memory during a commit.