About picotm
Picotm is a system-level transaction manager that allows you to write C applications and firmware that is secure, reliable and thread-safe; yet easy to develop.
Picotm applies the concept of transactions to low-level code and operating-system interfaces. Error handling and thread isolation are provided by picotm, all you have to write is the application logic.
Picotm is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Features
Picotm offers
- transactional semantics for arbitrary blocks of C code, and comes with
- built-in support for
- memory operations (Transactional Memory),
- transactional data structures (i.e., lists, multisets, queues, stacks),
- type casting and arithmetic that is safe from overflows, underflows and divisions by zero,
- memory allocation,
- file-descriptor I/O,
- C string and memory functions,
- C math functions,
- UNIX signal handling, and
- much more.
Picotm is implemented
- in portable C, and
- extensible to support the features and environments you require.
Currently supported are a variety of GNU/Linux systems, Windows (via Cygwin), MacOS X, and FreeBSD.
Example
The power of picotm is best explained by an example.
The following C program consists of two threads. Thread #1 writes a value to a globally shared memory location. Thread #2 reads a shared memory and saves it to a file and its backup.
The program uses two different types of resources: main memory and file descriptors.
int global_value; /* concurrently accessed by multiple threads */
pthread_mutex_t lock; /* protects |global_value| */
/* executed on thread #1 */
void
write_value(int value)
{
pthread_mutex_lock(&lock);
global_value = value;
pthread_mutex_unlock(&lock);
}
/* executed on thread #2 */
void
save_value(int fildes1, int fildes 2, off_t offset)
{
pthread_mutex_lock(&lock);
/* Save to file */
pwrite(fildes1, &global_value, sizeof(global_value), offset);
/* Save to backup file */
pwrite(fildes2, &global_value, sizeof(global_value), offset);
pthread_mutex_unlock(&lock);
}
The code above has a number of problems.
-
Program errors are not handled. If anything goes wrong, the example program wouldn’t know about it. In any software, error handling significantly complicates program logic and makes source code less readable. Since errors don’t happen during regular operation, existing error-handling code is often not well tested.
-
Calls to
pwrite()
are allowed to write less than the specified number of bytes. In this case, the caller has to invokepwrite()
again to write the remaining data until the buffer has been written entirely. -
The call to
pwrite()
forfildes2
can fail with an unrecoverable error, such as a full disk drive. The state is now inconsistent, as the original file contains the write, the backup file doesn’t. -
Mutexes are prone to deadlocks. If there’s another mutex in addition to
lock
and different threads acquire both mutexes in different order, the program can reach a state where threads are waiting for each other’s mutex to become available. This problem becomes more sever as more shared resources with corresponding locks are added. -
Locks don’t scale with the number of processors. If many threads read and write
global_value
concurrently, the locking operation will become a bottleneck for performance.
Here’s the same functionality implemented with picotm. With picotm, all
locking is replaced by transactions. Each transactions starts with
picotm_begin
and commits with picotm_commit
. Users can provide their own
error-recovery code between picotm_commit
and picotm_end
.
int global_value; /* concurrently accessed by multiple transactions */
/* executed on thread #1 */
void
write_value(int value)
{
picotm_begin
store_int_tx(&global_value, value);
picotm_commit
picotm_end
}
/* executed on thread #2 */
void
save_value(int fildes1, int fildes2, off_t offset)
{
picotm_begin
pwrite_tx(fildes1, &global_value, sizeof(global_value), offset);
pwrite_tx(fildes2, &global_value, sizeof(global_value), offset);
picotm_commit
if (picotm_error_is_non_recoverable()) {
/* Unrecoverable error */
inform_admin_and_shutdown_gracefully(); // provided by application
} else {
repair_error(); // provided by application
picotm_restart();
}
picotm_end
}
That’s it. Using picotm immediately fixes all of the problems present in the original source code.
-
All errors are detected by picotm. For recoverable errors, picotm will perform the recovery and restart the transaction. For unrecoverable errors the user can provide their own handling, or at least shutdown the program gracefully. All user error handling is in a single place after the commit operation.
-
Complete writes are performed by the implementation of
pwrite_tx().
Once executed, all data is written to the file; even in the precense of incomplete system calls and signal handling. -
Transactions are executed entirely, or not at all. If the second call to
pwrite_tx()
fails, the effects of the first call are reverted. Both files remain consistent. Picotm can take additional steps to avoid problems entirely, such as preallocating disk space before writing any data. -
Picotm automatically handles concurrency control for all its resources and operations. Store operations to
global_value
on thread #1 are executed transactionally, as well as calls topwrite_tx()
on thread #2. The value inglobal_value
is either written by thread #1 or read by thread #2, but never at the same time. Picotm also guarantees that the code is free from deadlocks. -
Finally, picotm can choose optimized strategies for concurrency control; depending on the resource, application and workload. It can adapt the granularity of its locking; or exploit semantics of higher-level operations to improve scalability.
This simple example only uses two different types of resources: main memory and file descriptors. Built-in support for both is provided as part of picotm (iff you want so). But picotm is a framework: you can add your own modules into the mix and have them integrated seamlessly with other transactional resources.
Downloading picotm
All information about getting picotm is available on the Downloads page. You can either download a stable release, or get a snapshot of the latest code.