Files
The Module Interface

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

Files

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 an instance of struct picotm_module_ops, which stores a number of module-specific call-back functions, and variable module data. It 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.

static const struct picotm_module_ops ops = {
.apply_event = apply, // See below.
.undo_event = undo // See below.
};
struct picotm_error error = PICOTM_ERROR_INITIALIZER;
unsigned long module_number = picotm_register_module(&ops, NULL, &error);
if (picotm_error_is_set(&error)) {
// abort with an error
}

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 wants 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(uint16_t head, uintptr_t tail, void* data, struct picotm_error* error)
{
if (head == CMD_MALLOC) {
// Nothing to do.
}
if (head == CMD_FREE) {
// Apply free().
free((void*)tail);
}
return 0;
}
int
undo(uint16_t head, uintptr_t tail, void* data, struct picotm_error* error)
{
if (head == CMD_MALLOC) {
// Revert malloc().
free((void*)tail);
}
if (head == CMD_FREE) {
// Nothing to do.
}
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, but creates an event to free the memory during a commit.