PMDK C++ bindings  1.13.0-git107.g7e59f08f
This is the C++ bindings documentation for PMDK's libpmemobj.
pmem::obj::flat_transaction Class Reference

C++ flat transaction handler class. More...

#include <libpmemobj++/transaction.hpp>

Inheritance diagram for pmem::obj::flat_transaction:

Public Types

using manual = typename detail::transaction_base< true >::manual
 C++ manual scope transaction class. More...
 
using automatic = typename detail::transaction_base< true >::automatic
 C++ automatic scope transaction class. More...
 
enum class  stage
 Possible stages of a transaction. More...
 

Public Member Functions

 ~flat_transaction () noexcept=delete
 Default destructor.
 

Static Public Member Functions

template<typename... Locks>
static void run (obj::pool_base &pool, std::function< void()> tx, Locks &... locks)
 Execute a closure-like transaction and lock locks. More...
 
static void abort (int err)
 Manually abort the current transaction. More...
 
static void commit ()
 Manually commit a transaction. More...
 
static int error () noexcept
 
static POBJ_CPP_DEPRECATED int get_last_tx_error () noexcept
 
static POBJ_CPP_DEPRECATED void exec_tx (obj::pool_base &pool, std::function< void()> tx, Locks &... locks)
 
static void snapshot (const T *addr, size_t num=1)
 Takes a “snapshot” of given elements of type T number (1 by default), located at the given address ptr in the virtual memory space and saves it to the undo log. More...
 
static void register_callback (stage stg, std::function< void()> cb)
 Registers callback to be called on specified stage for the transaction. More...
 

Detailed Description

C++ flat transaction handler class.

This class is recommended over basic_transaction.

This class is the pmemobj transaction handler. Scoped transactions are handled through two internal classes: manual and automatic.

  • manual transactions need to be committed manually, otherwise they will be aborted on object destruction.
  • automatic transactions are only available in C++17. They handle transaction commit/abort automatically.

This class also exposes a closure-like transaction API, which is the preferred way of handling transactions.

This API should NOT be mixed with C transactions API. One issue is that C++ callbacks registered using transaction::register_callback() would not be called if C++ transaction is created inside C transaction. The same is true if user calls pmemobj_tx_set_user_data() inside a C++ transaction.

Unlike basic_transaction, flat_transaction does not abort automatically in case of transactional functions (like make_persistent) failures. Instead, abort will happen only if an exception is not caught before the outermost transaction ends.

The typical usage example would be:

using namespace pmem::obj;
void
tx_flat_example()
{
/* pool root structure */
struct root {
p<int> count;
};
/* create a pmemobj pool */
auto pop = pool<root>::create("poolfile", "layout", PMEMOBJ_MIN_POOL);
auto proot = pop.root();
try {
proot->count++;
try {
proot->count++;
throw std::runtime_error("some error");
});
} catch (...) {
/* Transaction is not aborted yet (unlike for
* basic_transaction). */
assert(pmemobj_tx_stage() == TX_STAGE_WORK);
assert(proot->count == 2);
throw;
}
});
/* An internal transaction error occurred, outer tx aborted just
* now. Reacquire locks if necessary. */
assert(proot->count == 0);
} catch (...) {
/* Some other exception thrown, outer tx aborted just now.
* Reacquire locks if necessary. */
assert(proot->count == 0);
}
}
static void run(obj::pool_base &pool, std::function< void()> tx, Locks &... locks)
Execute a closure-like transaction and lock locks.
Definition: transaction.hpp:810
static pool< T > create(const std::string &path, const std::string &layout, std::size_t size=PMEMOBJ_MIN_POOL, mode_t mode=DEFAULT_MODE)
Creates a new transactional object store pool.
Definition: pool.hpp:694
Custom transaction error class.
Definition: pexceptions.hpp:109
persistent_ptr transactional allocation functions for objects.
Main libpmemobj namespace.
Definition: allocation_flag.hpp:18
Persistent smart pointer.
Convenience extensions for the resides on pmem property template.
C++ pmemobj pool.
C++ pmemobj transactions.
using namespace pmem::obj;
template <typename T>
struct simple_ptr {
simple_ptr()
{
assert(pmemobj_tx_stage() == TX_STAGE_WORK);
ptr = make_persistent<T>();
}
~simple_ptr()
{
assert(pmemobj_tx_stage() == TX_STAGE_WORK);
try {
delete_persistent<T>(ptr);
std::cerr << e.what() << std::endl;
std::terminate();
std::cerr << e.what() << std::endl;
std::terminate();
}
}
persistent_ptr<T> ptr;
};
struct A {
A() : ptr1(), ptr2()
{
}
simple_ptr<int> ptr1;
simple_ptr<char[(1ULL << 30)]> ptr2;
};
struct B {
B() : ptr1(), ptr2()
{
auto pop = pool_base(pmemobj_pool_by_ptr(this));
// It would result in a crash!
// basic_transaction::run(pop, [&]{ throw
// std::runtime_error("Error"); });
pop, [&] { throw std::runtime_error("Error"); });
}
simple_ptr<int> ptr1;
simple_ptr<int> ptr2;
};
void
tx_nested_struct_example()
{
/* pool root structure */
struct root {
persistent_ptr<A> ptrA;
persistent_ptr<B> ptrB;
};
/* create a pmemobj pool */
auto pop = pool<root>::create("poolfile", "layout", PMEMOBJ_MIN_POOL);
auto proot = pop.root();
auto create_a = [&] { proot->ptrA = make_persistent<A>(); };
auto create_b = [&] { proot->ptrB = make_persistent<B>(); };
try {
// It would result in a crash!
// basic_transaction::run(pop, create_a);
flat_transaction::run(pop, create_a);
/* To see why flat_transaction is necessary let's
* consider what happens when calling A ctor. The call stack
* will look like this:
*
* | ptr2 ctor |
* |-----------|
* | ptr1 ctor |
* |-----------|
* | A ctor |
*
* Since ptr2 is a pointer to some huge array of elements,
* calling ptr2 ctor will most likely result in make_persistent
* throwing an exception (due to out of memory). This exception
* will, in turn, cause stack unwinding - already constructed
* elements must be destroyed (in this example ptr1 destructor
* will be called).
*
* If we'd use basic_transaction the allocation failure, apart
* from throwing an exception, would also cause the transaction
* to abort (by default, in basic_transaction, all transactional
* functions failures cause tx abort). This is problematic since
* the ptr1 destructor, which is called during stack unwinding,
* expects the transaction to be in WORK stage (and the actual
* stage is ABORTED). As a result the application will fail on
* assert (and probably crash in NDEBUG mode).
*
* Now, consider what will happen if we'd use flat_transaction
* instead. In this case, make_persistent failure will not abort
* the transaction, it will only result in an exception. This
* means that the transaction is still in WORK stage during
* stack unwinding. Only after it completes, the transaction is
* aborted (it's happening at the outermost level, when exiting
* create_a lambda).
*/
} catch (std::runtime_error &) {
}
try {
basic_transaction::run(pop, create_b);
flat_transaction::run(pop, create_b);
/* Running create_b can be done both within basic and flat
* transaction. However, note that the transaction used in the B
* constructor MUST be a flat_transaction. This is because
* flat_transaction does not abort immediately when catching an
* exception. Instead it passes it to the outermost transaction
* - the abort is performed at that outermost level. In case of
* a basic_transaction the abort would be done within the B ctor
* and it would result in the same problems as with the previous
* example.
*/
} catch (std::runtime_error &) {
}
}
static void run(obj::pool_base &pool, std::function< void()> tx, Locks &... locks)
Execute a closure-like transaction and lock locks.
Definition: transaction.hpp:676
Custom transaction error class.
Definition: pexceptions.hpp:156
Custom transaction error class.
Definition: pexceptions.hpp:167

Member Typedef Documentation

◆ automatic

C++ automatic scope transaction class.

This class is one of pmemobj transaction handlers. All operations between creating and destroying the transaction object are treated as performed in a transaction block and can be rolled back. If you have a C++17 compliant compiler, the automatic transaction will commit and abort automatically depending on the context of object destruction.

The locks are held for the entire duration of the transaction. They are released at the end of the scope, so within the catch block, they are already unlocked. If the cleanup action requires access to data within a critical section, the locks have to be manually acquired once again.

The typical usage example would be:

using namespace pmem::obj;
int
automatic_tx_example()
{
/* pool root structure */
struct root {
mutex pmutex;
shared_mutex shared_pmutex;
p<int> count;
persistent_ptr<root> another_root;
};
/* create a pmemobj pool */
auto pop = pool<root>::create("poolfile", "layout", PMEMOBJ_MIN_POOL);
auto proot = pop.root();
try {
transaction::automatic tx(pop, proot->pmutex,
proot->shared_pmutex);
/* atomically allocate objects */
proot->another_root = make_persistent<root>();
/* atomically modify objects */
proot->count++;
/* manual transaction commit is no longer necessary */
/* an internal transaction error occurred, tx aborted
* reacquire locks if necessary */
} catch (...) {
/* some other exception thrown, tx aborted
* reacquire locks if necessary */
}
/* In complex cases with library calls, remember to check the status of
* the previous transaction. */
return transaction::error();
}
typename detail::transaction_base< false >::automatic automatic
C++ automatic scope transaction class.
Definition: transaction.hpp:669
Pmem-resident mutex.
Pmem-resident shared mutex.

◆ manual

C++ manual scope transaction class.

This class is one of pmemobj transaction handlers. All operations between creating and destroying the transaction object are treated as performed in a transaction block and can be rolled back. The manual transaction has to be committed explicitly in the outer most transaction - otherwise it will abort. Calling commit() in inner transactions is optional.

The locks are held for the entire duration of the transaction. They are released at the end of the scope, so within the catch block, they are already unlocked. If the cleanup action requires access to data within a critical section, the locks have to be manually acquired once again.

The typical usage example would be:

using namespace pmem::obj;
int
manual_flat_tx_example()
{
/* pool root structure */
struct root {
mutex pmutex;
shared_mutex shared_pmutex;
p<int> count;
persistent_ptr<root> another_root;
};
/* create a pmemobj pool */
auto pop = pool<root>::create("poolfile", "layout", PMEMOBJ_MIN_POOL);
auto proot = pop.root();
try {
flat_transaction::manual tx(pop, proot->pmutex);
/* atomically allocate objects */
proot->another_root = make_persistent<root>();
{
proot->shared_pmutex);
/* atomically modify objects */
proot->count++;
/* OPTIONAL */
// transaction::commit();
/* Even if there is no explicit commit inner_tx will
* not abort. This is true even if
* flat_transaction::manual is destroyed because of an
* active exception. For basic_transaction::manual you
* have to call commit() at each level (as many times as
* there are manual transaction objects). In case of
* a flat_transaction, the commit has to be called only
* once, at the outermost level.
*/
}
/* It's necessary to commit the transaction manually and
* it has to be the last operation in the transaction. */
/* An internal transaction error occurred, outer tx aborted just
* now. Reacquire locks if necessary, */
} catch (...) {
/* Some other exception thrown, outer tx aborted just now.
* Reacquire locks if necessary. */
}
/* In complex cases with library calls, remember to check the status of
* the last transaction. */
return transaction::error();
}
static void commit()
Manually commit a transaction.
Definition: transaction.hpp:330
typename detail::transaction_base< true >::manual manual
C++ manual scope transaction class.
Definition: transaction.hpp:745

Member Enumeration Documentation

◆ stage

Possible stages of a transaction.

For every stage one or more callbacks can be registered (see transaction::register_callback()).

To read more about PMDK's transactions and their stages, see manpage pmemobj_tx_begin(3): https://pmem.io/pmdk/manpages/linux/master/libpmemobj/pmemobj_tx_begin.3

Member Function Documentation

◆ abort()

static void pmem::detail::transaction_base< is_flat >::abort ( int  err)
inlinestaticinherited

Manually abort the current transaction.

If called within an inner transaction, the outer transactions will also be aborted.

Parameters
[in]errthe error to be reported as the reason of the abort.
Exceptions
transaction_errorif the transaction is in an invalid state.
manual_tx_abortthis exception is thrown to signify a transaction abort.

◆ commit()

static void pmem::detail::transaction_base< is_flat >::commit ( )
inlinestaticinherited

Manually commit a transaction.

It is the sole responsibility of the caller, that after the call to transaction::commit() no other operations are done within the transaction.

Exceptions
transaction_erroron any errors with ending the transaction.

◆ register_callback()

static void pmem::detail::transaction_base< is_flat >::register_callback ( stage  stg,
std::function< void()>  cb 
)
inlinestaticinherited

Registers callback to be called on specified stage for the transaction.

In case of nested transactions those callbacks are called when the outer most transaction enters a specified stage.

Precondition
this function must be called during a transaction.
Exceptions
transaction_scope_errorwhen called outside of a transaction scope

The typical usage example would be:

using namespace pmem::obj;
void
tx_callback_example()
{
/* pool root structure */
struct root {
p<int> count;
};
/* create a pmemobj pool */
auto pop = pool<root>::create("poolfile", "layout", PMEMOBJ_MIN_POOL);
bool cb_called = false;
auto internal_tx_function = [&] {
/* callbacks can be registered even in inner transaction but
* will be called when outer transaction ends */
transaction::run(pop, [&] {
transaction::stage::oncommit,
[&] { cb_called = true; });
});
/* cb_called is false here if internal_tx_function is called
* inside another transaction */
};
try {
transaction::run(pop, [&] { internal_tx_function(); });
/* cb_called == true if transaction ended successfully */
/* an internal transaction error occurred, tx aborted
* reacquire locks if necessary */
} catch (...) {
/* some other exception thrown, tx aborted
* reacquire locks if necessary */
}
}
static void register_callback(stage stg, std::function< void()> cb)
Registers callback to be called on specified stage for the transaction.
Definition: transaction.hpp:487

◆ run()

template<typename... Locks>
static void pmem::obj::flat_transaction::run ( obj::pool_base pool,
std::function< void()>  tx,
Locks &...  locks 
)
inlinestatic

Execute a closure-like transaction and lock locks.

Starts new transaction and executes passed tx function transactionally. Transaction can only start when stage is WORK or NONE.

The locks have to be persistent memory resident locks. An attempt to lock the locks will be made. If any of the specified locks is already locked, the method will block. The locks are held until the end of the transaction. The transaction does not have to be committed manually. Manual aborts will end the transaction with an active exception.

If an exception is thrown within the transaction, it gets propagated to the outer most transaction. If the exception is not caught, it will result in a transaction abort.

The locks are held for the entire duration of the transaction. They are released at the end of the scope, so within the catch block, they are already unlocked. If the cleanup action requires access to data within a critical section, the locks have to be manually acquired once again.

Parameters
[in,out]poolthe pool in which the transaction will take place.
[in]txan std::function<void ()> which will perform operations within this transaction.
[in,out]lockslocks to be taken for the duration of the transaction.
Exceptions
transaction_erroron any error pertaining the execution of the transaction.
manual_tx_aborton manual transaction abort.

◆ snapshot()

static void pmem::detail::transaction_base< is_flat >::snapshot ( const T *  addr,
size_t  num = 1 
)
inlinestaticinherited

Takes a “snapshot” of given elements of type T number (1 by default), located at the given address ptr in the virtual memory space and saves it to the undo log.

The application is then free to directly modify the object in that memory range. In case of a failure or abort, all the changes within this range will be rolled back. The supplied block of memory has to be within the pool registered in the transaction. This function must be called during transaction. This overload only participates in overload resolution of function template if T is either a trivially copyable type or some PMDK provided type.

Parameters
[in]addrpointer to the first object to be snapshotted.
[in]numnumber of elements to be snapshotted.
Precondition
this function must be called during transaction.
Exceptions
transaction_errorwhen snapshotting failed or if function wasn't called during transaction.

The documentation for this class was generated from the following file: