picotm
0.10.0
|
The transactional stack provides a transaction-safe implementation of a single-ended LIFO stack. Efficient insert and remove operations are supported on the same end. More...
Files | |
file | picotm-txstack-state.h |
Provides non-transactional state and entries for transactional stacks. | |
file | picotm-txstack.h |
Provides transactional stacks. | |
Stack entries are represented by struct txstack_entry
. We can add an instance of this data structure to any data value to turn it into a stack entry. Here's an example for stacks of values of type unsigned long
.
This code initializes the stack entry using txstack_entry_init()
. The macro TXSTACK_ENTRY_INITIALIZER
initializes static or stack-allocated stack entries. The example below illustrates this.
When both, macro and function initializers, are possible, the macro form is prefered. Stack entries are uninitialized with txstack_entry_uninit()
.
To store the stack entries, we need non-transactional stack state, which represents the shared state of a stack. It's implemented by struct txstack_state
. We can define and initialize a stack state as illustrated in the example below.
This code uses the initializer function txstack_state_init()
. For static or stack-allocated stack states, there's the initializer macro TXSTACK_STATE_INITIALIZER()
.
When both forms are possible, the initializer macro is prefered.
Stack-state clean-up is performed by txstack_state_uninit()
. The stack state may not contain entries when the clean-up happens. This means that entries have to be cleaned up from within a transaction.
For many uses, this requirement just imposes unnecessary overhead. A call to txstack_state_clear_and_uninit_entries()
provies a non-transactional way of clearing the stack from its entries. It erases each element from the stack and calls a clean-up function on it. The example below shows the clean-up code for a stack of struct ulong_item
entries.
At this point we have a stack state and stack entries to add to the state. So far all code was non-transactional. Actual stack access and manipulation is performed by transactional code.
To perform stack operations within a transaction, we first need a stack data structure for our transaction. It's represented by struct txstack
. A call to txstack_of_state_tx()
returns an instance.
Calling txstack_of_state_tx()
multiple times for the same stack state within the same transaction returns the same stack. Stacks are undefined after their transaction committed or aborted, or within other, concurrent transactions.
With the stack, we can now put entries onto the stack state using txstack_push_tx()
. The example below illustrates this.
After this transaction committed, the ulong data item item
will be the final entry in stack_state
. If the transactions has to abort after the call to txstack_push_tx()
, the transaction framework will automatically remove the pushed entry during the rollback; thus restoring the original state.
To remove the top-most entry from the stack, we can call txstack_pop_tx()
. This call is often combined with txstack_top_tx()
, which returns the stack's top-most entry. The combination is illustated in the example below.
As usual, all errors are detected and handled by the transaction framework. The benefits of transactional code show when we move entries between stacks.
In this example, we take an entry from a source stack and put it onto a destination stack. The transaction framework automatically isolates these operations from concurrent transactions until the transaction commits. So concurrent transactions see the entry on either the source stack or the destination stack, but never on both. If the transaction has to roll back after the push operation, the transaction framework automatically removes the stack entry from the destination stack and returns it to its old position on the source stack.
A call to txstack_size_tx()
returns the number of stack entries, a call to txstack_empty_tx()
returns true
if a stack is empty. The former function might have linear complexity, the later function always has constant complexity. It's therefore better to use txstack_empty_tx()
if it's only relevant whether there are entries.