From ecc116eb10fc7d8d5a14a36cf83d96c435fc9bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Chor=C4=85=C5=BCewicz?= Date: Thu, 24 Sep 2020 13:25:03 +0200 Subject: [PATCH 1/4] transaction: Introduce flat_transaction class Rename pmem::obj::transaction to pmem::obj::basic_transaction and make transaction a macro. The common code between basic_transaction and flat_transaction is moved to a base class. All the doxygen comments are placed next to the actual transaction implementation. --- examples/transaction/transaction.cpp | 260 +++++++++++ include/libpmemobj++/transaction.hpp | 469 ++++++++++++++------ tests/CMakeLists.txt | 13 +- tests/transaction/transaction.cpp | 145 ++---- tests/transaction/transaction.hpp | 33 ++ tests/transaction/transaction_basic.cpp | 381 ++++++++++++++++ tests/transaction/transaction_flat.cpp | 566 ++++++++++++++++++++++++ 7 files changed, 1627 insertions(+), 240 deletions(-) create mode 100644 tests/transaction/transaction.hpp create mode 100644 tests/transaction/transaction_basic.cpp create mode 100644 tests/transaction/transaction_flat.cpp diff --git a/examples/transaction/transaction.cpp b/examples/transaction/transaction.cpp index 55c8c2a06b..fc836e4ec3 100644 --- a/examples/transaction/transaction.cpp +++ b/examples/transaction/transaction.cpp @@ -226,6 +226,263 @@ tx_callback_example() } //! [tx_callback_example] +//! [tx_flat_example] +#include +#include +#include +#include +#include + +using namespace pmem::obj; + +void +tx_flat_example() +{ + /* pool root structure */ + struct root { + p count; + }; + + /* create a pmemobj pool */ + auto pop = pool::create("poolfile", "layout", PMEMOBJ_MIN_POOL); + auto proot = pop.root(); + + try { + flat_transaction::run(pop, [&] { + proot->count++; + + try { + flat_transaction::run(pop, [&] { + 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; + } + }); + } catch (pmem::transaction_error &) { + /* 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); + } +} +//! [tx_flat_example] + +//! [tx_nested_struct_example] +#include +#include +#include +#include +#include + +using namespace pmem::obj; + +template +struct simple_ptr { + simple_ptr() + { + assert(pmemobj_tx_stage() == TX_STAGE_WORK); + ptr = make_persistent(); + } + + ~simple_ptr() + { + assert(pmemobj_tx_stage() == TX_STAGE_WORK); + delete_persistent(ptr); + } + + persistent_ptr ptr; +}; + +/** + * This struct holds two simple_ptr. It presents problems when using + * basic_transaction in case of transactional function abort. */ +struct A { + A() : ptr1(), ptr2() + { + } + + simple_ptr ptr1; + simple_ptr ptr2; +}; + +/** + * This struct holds two simple_ptr. It presents problems when throwing + * exception from within basic_transaction. */ +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"); }); + + flat_transaction::run( + pop, [&] { throw std::runtime_error("Error"); }); + } + + simple_ptr ptr1; + simple_ptr ptr2; +}; + +void +tx_nested_struct_example() +{ + /* pool root structure */ + struct root { + persistent_ptr ptrA; + persistent_ptr ptrB; + }; + + /* create a pmemobj pool */ + auto pop = pool::create("poolfile", "layout", PMEMOBJ_MIN_POOL); + auto proot = pop.root(); + + auto create_a = [&] { proot->ptrA = make_persistent(); }; + auto create_b = [&] { proot->ptrB = make_persistent(); }; + + 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 &) { + } +} +//! [tx_nested_struct_example] + +//! [manual_flat_tx_example] +#include +#include +#include +#include +#include +#include +#include + +using namespace pmem::obj; + +int +manual_flat_tx_example() +{ + /* pool root structure */ + struct root { + mutex pmutex; + shared_mutex shared_pmutex; + p count; + persistent_ptr another_root; + }; + + /* create a pmemobj pool */ + auto pop = pool::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(); + + { + flat_transaction::manual inner_tx(pop, + 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. */ + transaction::commit(); + } catch (pmem::transaction_error &) { + /* 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(); +} +//! [manual_flat_tx_example] + int main() { @@ -234,6 +491,9 @@ main() manual_tx_example(); automatic_tx_example(); tx_callback_example(); + tx_flat_example(); + tx_nested_struct_example(); + manual_flat_tx_example(); } catch (const std::exception &e) { std::cerr << "Exception " << e.what() << std::endl; return -1; diff --git a/include/libpmemobj++/transaction.hpp b/include/libpmemobj++/transaction.hpp index 71f0b19c16..c5c16eb227 100644 --- a/include/libpmemobj++/transaction.hpp +++ b/include/libpmemobj++/transaction.hpp @@ -19,6 +19,10 @@ #include #include +#ifndef LIBPMEMOBJ_CPP_FLAT_TX_USE_FAILURE_RETURN +#define LIBPMEMOBJ_CPP_FLAT_TX_USE_FAILURE_RETURN true +#endif + namespace pmem { @@ -33,54 +37,12 @@ struct can_do_snapshot { static constexpr bool value = LIBPMEMOBJ_CPP_IS_TRIVIALLY_COPYABLE(T); }; -} /* namespace detail */ - -namespace obj -{ - /** - * C++ transaction handler class. - * - * This class is the pmemobj transaction handler. Scoped transactions - * are handled through two internal classes: @ref manual and - * @ref automatic. - * - @ref manual transactions need to be committed manually, otherwise - * they will be aborted on object destruction.\n - * - @ref 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. - * - * The typical usage example would be: - * @snippet transaction/transaction.cpp general_tx_example + * Common functionality for basic_transaction and flat_transaction. */ -class transaction { +template +class transaction_base { public: - /** - * 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 otherwise it will 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. - * - *The typical usage example would be: - * @snippet transaction/transaction.cpp manual_tx_example - */ class manual { public: /** @@ -101,14 +63,19 @@ class transaction { { int ret = 0; - if (pmemobj_tx_stage() == TX_STAGE_NONE) { - ret = pmemobj_tx_begin(pop.handle(), nullptr, - TX_PARAM_CB, - transaction::c_callback, - nullptr, TX_PARAM_NONE); - } else { + nested = pmemobj_tx_stage() == TX_STAGE_WORK; + + if (nested) { ret = pmemobj_tx_begin(pop.handle(), nullptr, TX_PARAM_NONE); + } else if (pmemobj_tx_stage() == TX_STAGE_NONE) { + ret = pmemobj_tx_begin( + pop.handle(), nullptr, TX_PARAM_CB, + transaction_base::c_callback, nullptr, + TX_PARAM_NONE); + } else { + throw pmem::transaction_scope_error( + "Cannot start transaction in stage different than WORK or NONE"); } if (ret != 0) @@ -125,20 +92,26 @@ class transaction { "failed to add lock") .with_pmemobj_errormsg(); } + + set_failure_behavior(); } /** * Destructor. * - * End pmemobj transaction. If the transaction has not - * been committed before object destruction, an abort - * will be issued. + * End pmemobj transaction. Abort the transaction if + * the transaction is not 'flat' or this is the outermost + * transaction. Commit the transaction otherwise. */ ~manual() noexcept { /* normal exit or with an active exception */ - if (pmemobj_tx_stage() == TX_STAGE_WORK) - pmemobj_tx_abort(ECANCELED); + if (pmemobj_tx_stage() == TX_STAGE_WORK) { + if (is_flat && nested) + pmemobj_tx_commit(); + else + pmemobj_tx_abort(ECANCELED); + } (void)pmemobj_tx_end(); } @@ -162,6 +135,24 @@ class transaction { * Deleted move assignment operator. */ manual &operator=(manual &&p) = delete; + + private: + template + typename std::enable_if::type + set_failure_behavior() + { + pmemobj_tx_set_failure_behavior(POBJ_TX_FAILURE_RETURN); + } + + template + typename std::enable_if::type + set_failure_behavior() + { + } + + bool nested = false; }; /* @@ -169,25 +160,6 @@ class transaction { * Test Recommendations. "|| _MSC_VER >= 1900" is a workaround. */ #if __cpp_lib_uncaught_exceptions || _MSC_VER >= 1900 - /** - * 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: - * @snippet transaction/transaction.cpp automatic_tx_example - */ class automatic { public: /** @@ -297,14 +269,14 @@ class transaction { int count; } exceptions; - transaction::manual tx_worker; + manual tx_worker; }; #endif /* __cpp_lib_uncaught_exceptions */ /* * Deleted default constructor. */ - transaction() = delete; + transaction_base() = delete; /** * Default destructor. @@ -312,7 +284,7 @@ class transaction { * End pmemobj transaction. If the transaction has not been * committed before object destruction, an abort will be issued. */ - ~transaction() noexcept = delete; + ~transaction_base() noexcept = delete; /** * Manually abort the current transaction. @@ -367,7 +339,7 @@ class transaction { POBJ_CPP_DEPRECATED static int get_last_tx_error() noexcept { - return transaction::error(); + return error(); } /** @@ -403,70 +375,30 @@ class transaction { */ template static void - run(pool_base &pool, std::function tx, Locks &... locks) + run(obj::pool_base &pool, std::function tx, Locks &... locks) { - int ret = 0; - - if (pmemobj_tx_stage() == TX_STAGE_NONE) { - ret = pmemobj_tx_begin(pool.handle(), nullptr, - TX_PARAM_CB, - transaction::c_callback, nullptr, - TX_PARAM_NONE); - } else { - ret = pmemobj_tx_begin(pool.handle(), nullptr, - TX_PARAM_NONE); - } - - if (ret != 0) - throw pmem::transaction_error( - "failed to start transaction") - .with_pmemobj_errormsg(); + manual worker(pool, locks...); - auto err = add_lock(locks...); - - if (err) { - pmemobj_tx_abort(err); - (void)pmemobj_tx_end(); - throw pmem::transaction_error( - "failed to add a lock to the transaction") - .with_pmemobj_errormsg(); - } - - try { - tx(); - } catch (manual_tx_abort &) { - (void)pmemobj_tx_end(); - throw; - } catch (...) { - /* first exception caught */ - if (pmemobj_tx_stage() == TX_STAGE_WORK) - pmemobj_tx_abort(ECANCELED); - - /* waterfall tx_end for outer tx */ - (void)pmemobj_tx_end(); - throw; - } + tx(); auto stage = pmemobj_tx_stage(); if (stage == TX_STAGE_WORK) { pmemobj_tx_commit(); } else if (stage == TX_STAGE_ONABORT) { - (void)pmemobj_tx_end(); throw pmem::transaction_error("transaction aborted"); } else if (stage == TX_STAGE_NONE) { throw pmem::transaction_error( "transaction ended prematurely"); } - - (void)pmemobj_tx_end(); } template POBJ_CPP_DEPRECATED static void - exec_tx(pool_base &pool, std::function tx, Locks &... locks) + exec_tx(obj::pool_base &pool, std::function tx, + Locks &... locks) { - transaction::run(pool, tx, locks...); + run(pool, tx, locks...); } /** @@ -643,6 +575,293 @@ class transaction { } }; +} /* namespace detail */ + +namespace obj +{ + +/** + * C++ transaction handler class. This class should be used with care. + * It's recommended to use pmem::obj::flat_transaction instead. + * + * This class is the pmemobj transaction handler. Scoped transactions + * are handled through two internal classes: @ref manual and + * @ref automatic. + * - @ref manual transactions need to be committed manually, otherwise + * they will be aborted on object destruction.\n + * - @ref 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. + * + * The typical usage example would be: + * @snippet transaction/transaction.cpp general_tx_example + */ +class basic_transaction : public detail::transaction_base { +public: + /** + * 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 otherwise it will 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. + * + * The typical usage example would be: + * @snippet transaction/transaction.cpp manual_tx_example + */ + using manual = typename detail::transaction_base::manual; + +/* + * XXX The Microsoft compiler does not follow the ISO SD-6: SG10 Feature + * Test Recommendations. "|| _MSC_VER >= 1900" is a workaround. + */ +#if __cpp_lib_uncaught_exceptions || _MSC_VER >= 1900 + /** + * 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: + * @snippet transaction/transaction.cpp automatic_tx_example + */ + using automatic = typename detail::transaction_base::automatic; +#endif /* __cpp_lib_uncaught_exceptions */ + /** + * Execute a closure-like transaction and lock `locks`. + * + * 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 aborted + * and the exception is rethrown. Therefore extra care has to be taken + * with proper error handling. + * + * 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. + * + * @param[in,out] pool the pool in which the transaction will take + * place. + * @param[in] tx an std::function which will perform + * operations within this transaction. + * @param[in,out] locks locks to be taken for the duration of + * the transaction. + * + * @throw transaction_error on any error pertaining the execution + * of the transaction. + * @throw manual_tx_abort on manual transaction abort. + */ + template + static void + run(obj::pool_base &pool, std::function tx, Locks &... locks) + { + return detail::transaction_base::run(pool, tx, locks...); + } + + /* + * Deleted default constructor. + */ + basic_transaction() = delete; + + /** + * Default destructor. + */ + ~basic_transaction() noexcept = delete; +}; + +/** + * 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: @ref manual and + * @ref automatic. + * - @ref manual transactions need to be committed manually, otherwise + * they will be aborted on object destruction.\n + * - @ref 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: + * @snippet transaction/transaction.cpp tx_flat_example + * @snippet transaction/transaction.cpp tx_nested_struct_example + * + */ +class flat_transaction : public detail::transaction_base { +public: + /** + * 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: + * @snippet transaction/transaction.cpp manual_flat_tx_example + */ + using manual = typename detail::transaction_base::manual; + +/* + * XXX The Microsoft compiler does not follow the ISO SD-6: SG10 Feature + * Test Recommendations. "|| _MSC_VER >= 1900" is a workaround. + */ +#if __cpp_lib_uncaught_exceptions || _MSC_VER >= 1900 + /** + * 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: + * @snippet transaction/transaction.cpp automatic_tx_example + */ + using automatic = typename detail::transaction_base::automatic; +#endif /* __cpp_lib_uncaught_exceptions */ + /** + * Execute a closure-like transaction and lock `locks`. + * + * 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. + * + * @param[in,out] pool the pool in which the transaction will take + * place. + * @param[in] tx an std::function which will perform + * operations within this transaction. + * @param[in,out] locks locks to be taken for the duration of + * the transaction. + * + * @throw transaction_error on any error pertaining the execution + * of the transaction. + * @throw manual_tx_abort on manual transaction abort. + */ + template + static void + run(obj::pool_base &pool, std::function tx, Locks &... locks) + { + return detail::transaction_base::run(pool, tx, locks...); + } + + /* + * Deleted default constructor. + */ + flat_transaction() = delete; + + /** + * Default destructor. + */ + ~flat_transaction() noexcept = delete; +}; + +#ifdef LIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION +/** + * By default, pmem::obj::transaction is an alias to + * pmem::obj::basic_transaction. To change it to pmem::obj::flat_transaction + * define LIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION macro. + * + * To see what is the difference between the two please look at the examples for + * flat tx: + * @snippet transaction/transaction.cpp tx_flat_example + * @snippet transaction/transaction.cpp tx_nested_struct_example + * + * and basic tx: + * @snippet transaction/transaction.cpp manual_tx_example + */ +using transaction = flat_transaction; +#else +/** + * By default, pmem::obj::transaction is an alias to + * pmem::obj::basic_transaction. To change it to pmem::obj::flat_transaction + * define LIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION macro. + * + * To see what is the difference between the two please look at the examples for + * flat tx: + * @snippet transaction/transaction.cpp tx_flat_example + * @snippet transaction/transaction.cpp tx_nested_struct_example + * + * and basic tx: + * @snippet transaction/transaction.cpp manual_tx_example + */ +using transaction = basic_transaction; +#endif + } /* namespace obj */ } /* namespace pmem */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6072fd8bd2..f296d581de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -187,8 +187,17 @@ if(NOT WIN32) add_test_generic(NAME shared_mutex_posix TRACERS drd helgrind pmemcheck) endif() -build_test(transaction transaction/transaction.cpp) -add_test_generic(NAME transaction TRACERS none pmemcheck memcheck) +build_test_ext(NAME transaction_common_flat SRC_FILES transaction/transaction.cpp BUILD_OPTIONS -DLIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION) +add_test_generic(NAME transaction_common_flat TRACERS none pmemcheck memcheck) + +build_test_ext(NAME transaction_common_basic SRC_FILES transaction/transaction.cpp) +add_test_generic(NAME transaction_common_basic TRACERS none pmemcheck memcheck) + +build_test_ext(NAME transaction_flat SRC_FILES transaction/transaction_flat.cpp BUILD_OPTIONS -DLIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION) +add_test_generic(NAME transaction_flat TRACERS none pmemcheck memcheck) + +build_test_ext(NAME transaction_basic SRC_FILES transaction/transaction_basic.cpp) +add_test_generic(NAME transaction_basic TRACERS none pmemcheck memcheck) if(VOLATILE_STATE_PRESENT) build_test(volatile_state volatile_state/volatile_state.cpp) diff --git a/tests/transaction/transaction.cpp b/tests/transaction/transaction.cpp index 190b299816..d6561f6c3d 100644 --- a/tests/transaction/transaction.cpp +++ b/tests/transaction/transaction.cpp @@ -5,6 +5,7 @@ * obj_cpp_transaction.cpp -- cpp transaction test */ +#include "transaction.hpp" #include "unittest.hpp" #include @@ -15,33 +16,6 @@ #include #include -namespace -{ -int counter = 0; -} - -/* - * XXX The Microsoft compiler does not follow the ISO SD-6: SG10 Feature - * Test Recommendations. "_MSC_VER" is a workaround. - */ -#if _MSC_VER < 1900 -#ifndef __cpp_lib_uncaught_exceptions -#define __cpp_lib_uncaught_exceptions 201411 -namespace std -{ - -int -uncaught_exceptions() noexcept -{ - return ::counter; -} - -} /* namespace std */ -#endif /* __cpp_lib_uncaught_exceptions */ -#endif /* _MSC_VER */ - -#include - #define LAYOUT "cpp" #define POOL_SIZE 2 * PMEMOBJ_MIN_POOL @@ -244,31 +218,6 @@ test_tx_throw_no_abort(nvobj::pool &pop) UT_ASSERT(rootp->pfoo == nullptr); UT_ASSERT(rootp->parr == nullptr); - try { - nvobj::transaction::run(pop, [&]() { - rootp->pfoo = nvobj::make_persistent(); - try { - nvobj::transaction::run(pop, [&]() { - throw std::runtime_error("error"); - }); - } catch (std::runtime_error &) { - exception_thrown = true; - } catch (...) { - UT_ASSERT(0); - } - UT_ASSERT(exception_thrown); - exception_thrown = false; - }); - } catch (pmem::transaction_error &) { - exception_thrown = true; - } catch (...) { - UT_ASSERT(0); - } - - UT_ASSERT(exception_thrown); - UT_ASSERT(rootp->pfoo == nullptr); - UT_ASSERT(rootp->parr == nullptr); - if (test_shared_mutex_self_deadlock()) { exception_thrown = false; rootp->shared_mutex.lock(); @@ -482,37 +431,6 @@ test_tx_throw_no_abort_scope(nvobj::pool &pop) UT_ASSERT(rootp->pfoo == nullptr); UT_ASSERT(rootp->parr == nullptr); - try { - counter = 0; - T to(pop); - rootp->pfoo = nvobj::make_persistent(); - try { - T to_nested(pop); - counter = 1; - throw std::runtime_error("error"); - } catch (std::runtime_error &) { - exception_thrown = true; - } catch (...) { - UT_ASSERT(0); - } - counter = 0; - UT_ASSERT(exception_thrown); - exception_thrown = false; - } catch (pmem::transaction_error &) { - exception_thrown = true; - } catch (...) { - UT_ASSERT(0); - } - - /* the transaction will be aborted silently */ - UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); - if (std::is_same::value) - UT_ASSERT(exception_thrown); - else - UT_ASSERT(!exception_thrown); - UT_ASSERT(rootp->pfoo == nullptr); - UT_ASSERT(rootp->parr == nullptr); - /* committing non-existent transaction should fail with an exception */ exception_thrown = false; try { @@ -551,6 +469,15 @@ test_tx_no_throw_abort_scope(nvobj::pool &pop) { auto rootp = pop.root(); + if (rootp->pfoo) { + nvobj::transaction::run(pop, [&]() { + nvobj::delete_persistent(rootp->pfoo); + nvobj::delete_persistent>(rootp->parr); + rootp->pfoo = nullptr; + rootp->parr = nullptr; + }); + } + UT_ASSERT(rootp->pfoo == nullptr); UT_ASSERT(rootp->parr == nullptr); @@ -724,34 +651,6 @@ test_tx_automatic_destructor_throw(nvobj::pool &pop) UT_ASSERT(exception_thrown); UT_ASSERT(rootp->pfoo == nullptr); UT_ASSERT(rootp->parr == nullptr); - - try { - counter = 0; - nvobj::transaction::automatic to(pop); - rootp->pfoo = nvobj::make_persistent(); - try { - nvobj::transaction::automatic to_nested(pop); - counter = 1; - throw std::runtime_error("error"); - } catch (std::runtime_error &) { - exception_thrown = true; - counter = 0; - } catch (...) { - UT_ASSERT(0); - } - UT_ASSERT(exception_thrown); - exception_thrown = false; - } catch (pmem::transaction_error &) { - exception_thrown = true; - } catch (...) { - UT_ASSERT(0); - } - - /* the transaction will be aborted silently */ - UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); - UT_ASSERT(exception_thrown); - UT_ASSERT(rootp->pfoo == nullptr); - UT_ASSERT(rootp->parr == nullptr); } /* @@ -1086,7 +985,7 @@ test_tx_callback_scope(nvobj::pool &pop, std::function commit) } void -test_tx_callback_outiside_tx() +test_tx_callback_outside_tx() { try { nvobj::transaction::register_callback( @@ -1099,6 +998,24 @@ test_tx_callback_outiside_tx() UT_ASSERT(0); } } + +void +test_tx_run_in_wrong_stage(nvobj::pool &pop) +{ + nvobj::transaction::run(pop, [&] { + nvobj::transaction::register_callback( + nvobj::transaction::stage::oncommit, [&] { + try { + nvobj::transaction::run( + pop, [&] { UT_ASSERT(0); }); + UT_ASSERT(0); + } catch (pmem::transaction_scope_error &) { + } catch (...) { + UT_ASSERT(0); + } + }); + }); +} } static void @@ -1139,7 +1056,9 @@ test(int argc, char *argv[]) test_tx_callback_scope(pop, real_commit); test_tx_callback_scope(pop, fake_commit); - test_tx_callback_outiside_tx(); + test_tx_callback_outside_tx(); + + test_tx_run_in_wrong_stage(pop); pop.close(); } diff --git a/tests/transaction/transaction.hpp b/tests/transaction/transaction.hpp new file mode 100644 index 0000000000..906e0d6941 --- /dev/null +++ b/tests/transaction/transaction.hpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright 2020, Intel Corporation */ + +#ifndef LIBPMEMOBJ_CPP_TESTS_TRANSACTION +#define LIBPMEMOBJ_CPP_TESTS_TRANSACTION + +#include + +namespace +{ +int counter = 0; +} + +/* + * XXX The Microsoft compiler does not follow the ISO SD-6: SG10 Feature + * Test Recommendations. "_MSC_VER" is a workaround. + */ +#if _MSC_VER < 1900 +#ifndef __cpp_lib_uncaught_exceptions +#define __cpp_lib_uncaught_exceptions 201411 +namespace std +{ + +int +uncaught_exceptions() noexcept +{ + return ::counter; +} + +} /* namespace std */ +#endif /* __cpp_lib_uncaught_exceptions */ +#endif /* _MSC_VER */ +#endif /* LIBPMEMOBJ_CPP_TESTS_TRANSACTION */ diff --git a/tests/transaction/transaction_basic.cpp b/tests/transaction/transaction_basic.cpp new file mode 100644 index 0000000000..51dbd1e106 --- /dev/null +++ b/tests/transaction/transaction_basic.cpp @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright 2020, Intel Corporation */ + +#include "transaction.hpp" +#include "unittest.hpp" + +#include +#include +#include + +namespace nvobj = pmem::obj; + +using huge_object = char[1ULL << 30]; + +struct root { + nvobj::persistent_ptr p1; + nvobj::persistent_ptr p2; +}; + +namespace +{ +void +test_tx_throw_no_abort(nvobj::pool &pop) +{ + auto r = pop.root(); + + bool exception_thrown = false; + + try { + nvobj::transaction::run(pop, [&]() { + r->p1 = nvobj::make_persistent(); + try { + nvobj::transaction::run(pop, [&]() { + r->p2 = nvobj::make_persistent(); + throw std::runtime_error("error"); + }); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == + TX_STAGE_ONABORT); + + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); + + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + }); + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(exception_thrown); + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); +} + +void +test_tx_nested_behavior(nvobj::pool &pop) +{ + auto r = pop.root(); + + bool exception_thrown = false; + + try { + nvobj::transaction::run(pop, [&]() { + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_ABORT); + + r->p1 = nvobj::make_persistent(); + try { + nvobj::flat_transaction::run(pop, [&]() { + UT_ASSERT( + pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p2 = nvobj::make_persistent(); + throw std::runtime_error("error"); + }); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == TX_STAGE_WORK); + + UT_ASSERT(r->p1 != nullptr); + UT_ASSERT(r->p2 != nullptr); + + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + }); + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), 0); + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 != nullptr); + UT_ASSERT(r->p2 != nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + nvobj::delete_persistent(r->p2); + + r->p1 = nullptr; + r->p2 = nullptr; + }); + + exception_thrown = false; + + try { + nvobj::transaction::run(pop, [&]() { + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_ABORT); + + r->p1 = nvobj::make_persistent(); + + nvobj::flat_transaction::run(pop, [&]() { + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p2 = nvobj::make_persistent(); + throw std::runtime_error("error"); + }); + }); + } catch (std::runtime_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + UT_ASSERT(exception_thrown); + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); +} + +template +void +test_tx_nested_behavior_scope(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + OuterBasicTx to(pop); + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_ABORT); + + r->p1 = nvobj::make_persistent(); + try { + InnerFlatTx to_nested(pop); + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p2 = nvobj::make_persistent(); + counter = 1; + throw std::runtime_error("error"); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == TX_STAGE_WORK); + + UT_ASSERT(r->p1 != nullptr); + UT_ASSERT(r->p2 != nullptr); + + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + if (std::is_same::value) + nvobj::transaction::commit(); + + UT_ASSERT(exception_thrown); + exception_thrown = false; + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), 0); + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 != nullptr); + UT_ASSERT(r->p2 != nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + nvobj::delete_persistent(r->p2); + + r->p1 = nullptr; + r->p2 = nullptr; + }); + + exception_thrown = false; + + try { + counter = 0; + OuterBasicTx to(pop); + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_ABORT); + + r->p1 = nvobj::make_persistent(); + { + InnerFlatTx to_nested(pop); + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p2 = nvobj::make_persistent(); + counter = 1; + throw std::runtime_error("error"); + } + } catch (std::runtime_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + UT_ASSERT(exception_thrown); + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); +} + +template +void +test_tx_throw_no_abort_scope(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + T to(pop); + r->p1 = nvobj::make_persistent(); + try { + T to_nested(pop); + r->p2 = nvobj::make_persistent(); + counter = 1; + throw std::runtime_error("error"); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == TX_STAGE_ONABORT); + + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); + + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + /* the transaction will be aborted silently */ + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + if (std::is_same::value) + UT_ASSERT(exception_thrown); + else + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 == nullptr); + UT_ASSERT(r->p2 == nullptr); +} + +void +test_tx_automatic_destructor_throw(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + nvobj::transaction::automatic to(pop); + r->p1 = nvobj::make_persistent(); + try { + nvobj::transaction::automatic to_nested(pop); + counter = 1; + throw std::runtime_error("error"); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == TX_STAGE_ONABORT); + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + /* the transaction will be aborted silently */ + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + UT_ASSERT(exception_thrown); + UT_ASSERT(r->p1 == nullptr); +} + +/* + * test_tx_manual_no_commit -- test manual transaction with no commit + */ +void +test_tx_manual_no_commit(nvobj::pool &pop) +{ + auto r = pop.root(); + + try { + nvobj::transaction::manual tx(pop); + r->p1 = nvobj::make_persistent(); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(r->p1 == nullptr); + + try { + nvobj::transaction::manual tx1(pop); + { + nvobj::transaction::manual tx2(pop); + r->p1 = nvobj::make_persistent(); + } + + UT_ASSERT(r->p1 == nullptr); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(r->p1 == nullptr); +} + +void +test(int argc, char *argv[]) +{ + if (argc < 2) + UT_FATAL("usage: %s file-name", argv[0]); + + auto path = argv[1]; + auto pop = + nvobj::pool::create(path, "transaction_noabort", + PMEMOBJ_MIN_POOL, S_IWUSR | S_IRUSR); + + test_tx_throw_no_abort(pop); + + test_tx_nested_behavior(pop); + + test_tx_nested_behavior_scope(pop); + test_tx_nested_behavior_scope(pop); + + test_tx_throw_no_abort_scope(pop); + test_tx_throw_no_abort_scope(pop); + + test_tx_automatic_destructor_throw(pop); + + test_tx_manual_no_commit(pop); + + pop.close(); +} +} + +int +main(int argc, char *argv[]) +{ + return run_test([&] { test(argc, argv); }); +} diff --git a/tests/transaction/transaction_flat.cpp b/tests/transaction/transaction_flat.cpp new file mode 100644 index 0000000000..3f90db5348 --- /dev/null +++ b/tests/transaction/transaction_flat.cpp @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright 2020, Intel Corporation */ + +#include "transaction.hpp" +#include "unittest.hpp" + +#include +#include +#include +#include + +namespace nvobj = pmem::obj; + +using huge_object = char[1ULL << 30]; + +const int ABORT_VAL = 0xABC; + +template +struct simple_ptr { + simple_ptr() + { + ptr = nvobj::make_persistent(); + } + ~simple_ptr() + { + UT_ASSERT(ptr != nullptr); + + nvobj::delete_persistent(ptr); + } + + nvobj::persistent_ptr ptr; +}; + +template +struct simple_ptr_tx { + simple_ptr_tx() + { + auto pop = nvobj::pool_base(pmemobj_pool_by_ptr(this)); + + nvobj::transaction::run( + pop, [&] { ptr = nvobj::make_persistent(); }); + } + ~simple_ptr_tx() + { + UT_ASSERT(ptr != nullptr); + + nvobj::delete_persistent(ptr); + } + + nvobj::persistent_ptr ptr; +}; + +template +struct simple_ptr_explicit_abort { + static nvobj::pool_base *pop; + + simple_ptr_explicit_abort(nvobj::pool_base &pop) + { + simple_ptr_explicit_abort::pop = &pop; + + ptr = nvobj::make_persistent(); + } + + ~simple_ptr_explicit_abort() + { + auto oid = pmemobj_first(pop->handle()); + UT_ASSERT(OID_IS_NULL(oid)); + } + + nvobj::persistent_ptr ptr; +}; + +template +nvobj::pool_base *simple_ptr_explicit_abort::pop = nullptr; + +struct C { + C() : b() + { + nvobj::make_persistent(); + } + + simple_ptr b; +}; + +struct C_tx { + C_tx() : b() + { + nvobj::make_persistent(); + } + + simple_ptr_tx b; +}; + +struct C_explicit_abort { + C_explicit_abort(nvobj::pool_base &pop) : b(pop) + { + nvobj::transaction::abort(ABORT_VAL); + } + + simple_ptr_explicit_abort b; +}; + +struct C_nested { + C_nested() : b() + { + nvobj::make_persistent(); + } + + simple_ptr> b; +}; + +struct root { + nvobj::persistent_ptr c_ptr; + nvobj::persistent_ptr c_ptr_tx; + nvobj::persistent_ptr c_nested_ptr; + nvobj::persistent_ptr c_explicit_abort_ptr; + + nvobj::persistent_ptr p1; + nvobj::persistent_ptr p2; + nvobj::persistent_ptr p3; +}; + +namespace +{ + +void +test_dtor_after_tx_abort(nvobj::pool &pop) +{ + try { + nvobj::transaction::run(pop, [&] { + pop.root()->c_ptr = nvobj::make_persistent(); + }); + + UT_ASSERT(0); + } catch (pmem::transaction_alloc_error &) { + } catch (std::exception &e) { + UT_FATALexc(e); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + auto oid = pmemobj_first(pop.handle()); + UT_ASSERT(OID_IS_NULL(oid)); + + try { + nvobj::transaction::run(pop, [&] { + pop.root()->c_ptr_tx = nvobj::make_persistent(); + }); + + UT_ASSERT(0); + } catch (pmem::transaction_alloc_error &) { + } catch (std::exception &e) { + UT_FATALexc(e); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + oid = pmemobj_first(pop.handle()); + UT_ASSERT(OID_IS_NULL(oid)); +} + +void +test_dtor_after_tx_explicit_abort(nvobj::pool &pop) +{ + try { + nvobj::transaction::run(pop, [&] { + pop.root()->c_explicit_abort_ptr = + nvobj::make_persistent(pop); + }); + + UT_ASSERT(0); + } catch (pmem::manual_tx_abort &) { + } catch (std::exception &e) { + UT_FATALexc(e); + } + + UT_ASSERTeq(nvobj::transaction::error(), ABORT_VAL); + auto oid = pmemobj_first(pop.handle()); + UT_ASSERT(OID_IS_NULL(oid)); +} + +void +test_nested_dtor_after_tx_abort(nvobj::pool &pop) +{ + try { + nvobj::transaction::run(pop, [&] { + pop.root()->c_nested_ptr = + nvobj::make_persistent(); + }); + + UT_ASSERT(0); + } catch (pmem::transaction_alloc_error &) { + } catch (std::exception &e) { + UT_FATALexc(e); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + auto oid = pmemobj_first(pop.handle()); + UT_ASSERT(OID_IS_NULL(oid)); +} + +void +test_ignore_exception(nvobj::pool &pop) +{ + auto r = pop.root(); + + try { + nvobj::transaction::run(pop, [&] { + r->p1 = nvobj::make_persistent(); + r->p2 = nvobj::make_persistent(); + r->p3 = nvobj::make_persistent(); + + try { + nvobj::make_persistent(); + } catch (...) { + /* ignore exception */ + } + }); + + /* p1, p2, p3 are still accessible */ + UT_ASSERTne(pmemobj_pool_by_oid(r->p1.raw()), nullptr); + UT_ASSERTne(pmemobj_pool_by_oid(r->p2.raw()), nullptr); + UT_ASSERTne(pmemobj_pool_by_oid(r->p3.raw()), nullptr); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), 0); + /* p1, p2, p3 are still accessible */ + UT_ASSERTne(pmemobj_pool_by_oid(r->p1.raw()), nullptr); + UT_ASSERTne(pmemobj_pool_by_oid(r->p2.raw()), nullptr); + UT_ASSERTne(pmemobj_pool_by_oid(r->p3.raw()), nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + nvobj::delete_persistent(r->p2); + nvobj::delete_persistent(r->p3); + + r->p1 = nullptr; + r->p2 = nullptr; + r->p3 = nullptr; + }); +} + +void +test_memory_is_freed_explicit_abort(nvobj::pool &pop) +{ + auto r = pop.root(); + + try { + nvobj::transaction::run(pop, [&] { + r->p1 = nvobj::make_persistent(); + r->p2 = nvobj::make_persistent(); + r->p3 = nvobj::make_persistent(); + + try { + nvobj::transaction::abort(-1); + } catch (...) { + /* ignore exception */ + } + + UT_ASSERTeq(nvobj::transaction::error(), -1); + }); + + UT_ASSERT(0); + } catch (pmem::transaction_error &) { + } catch (std::exception &e) { + UT_FATALexc(e); + } + + UT_ASSERTeq(nvobj::transaction::error(), -1); + auto oid = pmemobj_first(pop.handle()); + UT_ASSERT(OID_IS_NULL(oid)); +} + +void +test_tx_throw_no_abort(nvobj::pool &pop) +{ + auto r = pop.root(); + + bool exception_thrown = false; + + try { + nvobj::transaction::run(pop, [&]() { + r->p1 = nvobj::make_persistent(); + try { + nvobj::transaction::run(pop, [&]() { + throw std::runtime_error("error"); + }); + } catch (std::runtime_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + }); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), 0); + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 != nullptr); + UT_ASSERTne(pmemobj_pool_by_oid(r->p1.raw()), nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + r->p1 = nullptr; + }); +} + +void +test_tx_nested_behavior(nvobj::pool &pop) +{ + auto r = pop.root(); + + bool exception_thrown = false; + + try { + nvobj::transaction::run(pop, [&]() { + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p1 = nvobj::make_persistent(); + try { + nvobj::basic_transaction::run(pop, [&]() { + throw std::runtime_error("error"); + }); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == + TX_STAGE_ONABORT); + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + }); + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTne(nvobj::transaction::error(), 0); + UT_ASSERT(exception_thrown); + UT_ASSERT(r->p1 == nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + r->p1 = nullptr; + }); +} + +template +void +test_tx_nested_behavior_scope(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + OuterFlatTx to(pop); + UT_ASSERT(pmemobj_tx_get_failure_behavior() == + POBJ_TX_FAILURE_RETURN); + + r->p1 = nvobj::make_persistent(); + try { + InnerBasicTx to_nested(pop); + counter = 1; + throw std::runtime_error("error"); + UT_ASSERT(0); + } catch (std::runtime_error &) { + UT_ASSERT(pmemobj_tx_stage() == TX_STAGE_ONABORT); + + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(exception_thrown); + exception_thrown = false; + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), ECANCELED); + + if (std::is_same::value) + UT_ASSERT(exception_thrown); + else + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 == nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + r->p1 = nullptr; + }); +} + +template +void +test_tx_throw_no_abort_scope(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + T to(pop); + r->p1 = nvobj::make_persistent(); + try { + T to_nested(pop); + counter = 1; + throw std::runtime_error("error"); + } catch (std::runtime_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(exception_thrown); + exception_thrown = false; + + if (std::is_same::value) + nvobj::transaction::commit(); + + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERTeq(nvobj::transaction::error(), 0); + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 != nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + r->p1 = nullptr; + }); +} + +void +test_tx_automatic_destructor_throw(nvobj::pool &pop) +{ + bool exception_thrown = false; + + auto r = pop.root(); + + try { + counter = 0; + nvobj::transaction::automatic to(pop); + r->p1 = nvobj::make_persistent(); + try { + nvobj::transaction::automatic to_nested(pop); + counter = 1; + throw std::runtime_error("error"); + } catch (std::runtime_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + UT_ASSERT(exception_thrown); + exception_thrown = false; + } catch (pmem::transaction_error &) { + exception_thrown = true; + } catch (...) { + UT_ASSERT(0); + } + + /* the transaction won't be aborted since exception was handled */ + UT_ASSERTeq(nvobj::transaction::error(), 0); + UT_ASSERT(!exception_thrown); + UT_ASSERT(r->p1 != nullptr); + + nvobj::transaction::run(pop, [&] { + nvobj::delete_persistent(r->p1); + r->p1 = nullptr; + }); +} + +/* + * test_tx_manual_no_commit -- test manual transaction with no commit + */ +void +test_tx_manual_no_commit(nvobj::pool &pop) +{ + auto r = pop.root(); + + try { + nvobj::transaction::manual tx(pop); + r->p1 = nvobj::make_persistent(); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(r->p1 == nullptr); + + try { + nvobj::transaction::manual tx1(pop); + { + nvobj::transaction::manual tx2(pop); + r->p1 = nvobj::make_persistent(); + } + + /* For flat transactions, it's not necessary to call commit for + * inner transactions. */ + UT_ASSERT(r->p1 != nullptr); + } catch (...) { + UT_ASSERT(0); + } + + UT_ASSERT(r->p1 == nullptr); +} + +void +test(int argc, char *argv[]) +{ + if (argc < 2) + UT_FATAL("usage: %s file-name", argv[0]); + + auto path = argv[1]; + auto pop = + nvobj::pool::create(path, "transaction_noabort", + PMEMOBJ_MIN_POOL, S_IWUSR | S_IRUSR); + + test_ignore_exception(pop); + + test_tx_throw_no_abort(pop); + + test_memory_is_freed_explicit_abort(pop); + + test_dtor_after_tx_explicit_abort(pop); + + test_dtor_after_tx_abort(pop); + test_nested_dtor_after_tx_abort(pop); + + test_tx_nested_behavior(pop); + + test_tx_nested_behavior_scope(pop); + test_tx_nested_behavior_scope(pop); + + test_tx_throw_no_abort_scope(pop); + test_tx_throw_no_abort_scope(pop); + + test_tx_automatic_destructor_throw(pop); + + test_tx_manual_no_commit(pop); + + pop.close(); +} +} + +int +main(int argc, char *argv[]) +{ + return run_test([&] { test(argc, argv); }); +} From 3fb38eda11af01ed89b3fb9c18cc1ae39fea8e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Chor=C4=85=C5=BCewicz?= Date: Mon, 12 Oct 2020 15:09:29 +0200 Subject: [PATCH 2/4] Use flat_transaction in all containers by default. --- include/libpmemobj++/container/array.hpp | 8 +-- .../libpmemobj++/container/basic_string.hpp | 38 +++++++------- .../container/concurrent_hash_map.hpp | 50 +++++++++---------- .../detail/concurrent_skip_list_impl.hpp | 41 +++++++-------- .../libpmemobj++/container/segment_vector.hpp | 34 ++++++------- include/libpmemobj++/container/vector.hpp | 34 ++++++------- .../detail/enumerable_thread_specific.hpp | 2 +- .../libpmemobj++/detail/volatile_state.hpp | 4 +- .../experimental/inline_string.hpp | 2 +- .../libpmemobj++/experimental/radix_tree.hpp | 32 ++++++------ 10 files changed, 124 insertions(+), 121 deletions(-) diff --git a/include/libpmemobj++/container/array.hpp b/include/libpmemobj++/container/array.hpp index 4370e2b3f9..a6766faba6 100644 --- a/include/libpmemobj++/container/array.hpp +++ b/include/libpmemobj++/container/array.hpp @@ -120,7 +120,7 @@ struct array { if (this == &other) return *this; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { detail::conditional_add_to_tx( this, 1, POBJ_XADD_ASSUME_INITIALIZED); std::copy(other.cbegin(), other.cend(), _get_data()); @@ -151,7 +151,7 @@ struct array { if (this == &other) return *this; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { detail::conditional_add_to_tx( this, 1, POBJ_XADD_ASSUME_INITIALIZED); detail::conditional_add_to_tx( @@ -588,7 +588,7 @@ struct array { { auto pop = _get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { detail::conditional_add_to_tx( this, 1, POBJ_XADD_ASSUME_INITIALIZED); std::fill(_get_data(), _get_data() + size(), value); @@ -615,7 +615,7 @@ struct array { if (this == &other) return; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { detail::conditional_add_to_tx( this, 1, POBJ_XADD_ASSUME_INITIALIZED); detail::conditional_add_to_tx( diff --git a/include/libpmemobj++/container/basic_string.hpp b/include/libpmemobj++/container/basic_string.hpp index 88f6a44b1d..b92146f6f5 100644 --- a/include/libpmemobj++/container/basic_string.hpp +++ b/include/libpmemobj++/container/basic_string.hpp @@ -906,7 +906,7 @@ basic_string::assign(size_type count, CharT ch) { auto pop = get_pool(); - transaction::run(pop, [&] { replace_content(count, ch); }); + flat_transaction::run(pop, [&] { replace_content(count, ch); }); return *this; } @@ -929,7 +929,7 @@ basic_string::assign(const basic_string &other) auto pop = get_pool(); - transaction::run( + flat_transaction::run( pop, [&] { replace_content(other.cbegin(), other.cend()); }); return *this; @@ -979,7 +979,7 @@ basic_string::assign(const basic_string &other, size_type pos, auto first = static_cast(pos); auto last = first + static_cast(count); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { replace_content(other.cbegin() + first, other.cbegin() + last); }); @@ -1030,7 +1030,7 @@ basic_string::assign(const CharT *s, size_type count) { auto pop = get_pool(); - transaction::run(pop, [&] { replace_content(s, s + count); }); + flat_transaction::run(pop, [&] { replace_content(s, s + count); }); return *this; } @@ -1051,7 +1051,7 @@ basic_string::assign(const CharT *s) auto length = traits_type::length(s); - transaction::run(pop, [&] { replace_content(s, s + length); }); + flat_transaction::run(pop, [&] { replace_content(s, s + length); }); return *this; } @@ -1074,7 +1074,7 @@ basic_string::assign(InputIt first, InputIt last) { auto pop = get_pool(); - transaction::run(pop, [&] { replace_content(first, last); }); + flat_transaction::run(pop, [&] { replace_content(first, last); }); return *this; } @@ -1097,7 +1097,7 @@ basic_string::assign(basic_string &&other) auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { destroy_data(); move_data(std::move(other)); }); @@ -1633,7 +1633,7 @@ basic_string::erase(size_type index, size_type count) auto last = first + static_cast(count); if (is_sso_used()) { - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { auto move_len = sz - index - count; auto new_size = sz - count; @@ -1760,7 +1760,7 @@ basic_string::append(size_type count, CharT ch) if (is_sso_used()) { auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (new_size > sso_capacity) { sso_to_large(new_size); @@ -1943,7 +1943,7 @@ basic_string::append(InputIt first, InputIt last) if (is_sso_used()) { auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (new_size > sso_capacity) { /* 1) Cache C-style string in case of * self-append, because it will be destroyed @@ -2360,7 +2360,7 @@ basic_string::insert(const_iterator pos, size_type count, auto index = static_cast(std::distance(cbegin(), pos)); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (is_sso_used() && new_size <= sso_capacity) { auto len = sz - index; @@ -2431,7 +2431,7 @@ basic_string::insert(const_iterator pos, InputIt first, auto index = static_cast(std::distance(cbegin(), pos)); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (is_sso_used() && new_size <= sso_capacity) { auto len = sz - index; @@ -2651,7 +2651,7 @@ basic_string::replace(const_iterator first, const_iterator last, auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (is_sso_used() && new_size <= sso_capacity) { add_sso_to_tx(index, new_size - index + 1); @@ -2867,7 +2867,7 @@ basic_string::replace(const_iterator first, const_iterator last, auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (is_sso_used() && new_size <= sso_capacity) { add_sso_to_tx(index, new_size - index + 1); @@ -3748,7 +3748,7 @@ basic_string::resize(size_type count, CharT ch) auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (count > sz) { append(count - sz, ch); } else if (is_sso_used()) { @@ -3819,7 +3819,7 @@ basic_string::reserve(size_type new_cap) if (is_sso_used()) { auto pop = get_pool(); - transaction::run(pop, [&] { sso_to_large(new_cap); }); + flat_transaction::run(pop, [&] { sso_to_large(new_cap); }); } else { non_sso_data().reserve(new_cap + 1); } @@ -3848,7 +3848,7 @@ basic_string::shrink_to_fit() if (size() <= sso_capacity) { auto pop = get_pool(); - transaction::run(pop, [&] { large_to_sso(); }); + flat_transaction::run(pop, [&] { large_to_sso(); }); } else { non_sso_data().shrink_to_fit(); } @@ -3888,7 +3888,7 @@ basic_string::free_data() { auto pop = get_pool(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { if (is_sso_used()) { add_sso_to_tx(0, get_sso_size() + 1); clear(); @@ -4176,7 +4176,7 @@ void basic_string::swap(basic_string &other) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (is_sso_used() && other.is_sso_used()) { sso_data().swap(other.sso_data()); pmem::obj::swap(sso._size, other.sso._size); diff --git a/include/libpmemobj++/container/concurrent_hash_map.hpp b/include/libpmemobj++/container/concurrent_hash_map.hpp index fb8a579c73..92c7289e14 100644 --- a/include/libpmemobj++/container/concurrent_hash_map.hpp +++ b/include/libpmemobj++/container/concurrent_hash_map.hpp @@ -700,7 +700,7 @@ class segment_facade_impl : public SegmentTraits { { assert(my_seg == embedded_segments); { - transaction::manual tx(pop); + flat_transaction::manual tx(pop); size_type sz = segment_size(first_block) - embedded_buckets; @@ -720,7 +720,7 @@ class segment_facade_impl : public SegmentTraits { (*my_table)[s] = (base + off).raw(); } - transaction::commit(); + flat_transaction::commit(); } } @@ -729,7 +729,7 @@ class segment_facade_impl : public SegmentTraits { { block_range blocks = segment_blocks(my_seg); { - transaction::manual tx(pop); + flat_transaction::manual tx(pop); for (segment_index_t b = blocks.first; b < blocks.second; ++b) { @@ -738,7 +738,7 @@ class segment_facade_impl : public SegmentTraits { block_size(b)); } - transaction::commit(); + flat_transaction::commit(); } } @@ -974,7 +974,7 @@ class hash_map_base { static_cast(static_cast(last_run_size) + on_init_size) >= 0); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { on_init_size += static_cast(last_run_size); tls_ptr->clear(); }); @@ -1038,7 +1038,7 @@ class hash_map_base { if ((layout_features.compat & FEATURE_CONSISTENT_SIZE) && tls_ptr) { - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { delete_persistent(tls_ptr); tls_ptr = nullptr; }); @@ -1252,7 +1252,7 @@ class hash_map_base { } else { auto &size_diff = thread_size_diff(); - pmem::obj::transaction::run(pop, [&] { + pmem::obj::flat_transaction::run(pop, [&] { insert_new_node_internal( b, new_node, std::forward(args)...); @@ -1326,7 +1326,7 @@ class hash_map_base { { pool_base p = get_pool_base(); { - transaction::manual tx(p); + flat_transaction::manual tx(p); this->my_pool_uuid.swap(table.my_pool_uuid); @@ -1336,8 +1336,8 @@ class hash_map_base { * transaction we must make sure that mask and size * changes are transactional */ - transaction::snapshot((size_t *)&this->my_mask); - transaction::snapshot((size_t *)&this->my_size); + flat_transaction::snapshot((size_t *)&this->my_mask); + flat_transaction::snapshot((size_t *)&this->my_size); this->mask() = table.mask().exchange( this->mask(), std::memory_order_relaxed); @@ -1356,7 +1356,7 @@ class hash_map_base { i < block_table_size; ++i) this->my_table[i].swap(table.my_table[i]); - transaction::commit(); + flat_transaction::commit(); } } @@ -1906,7 +1906,7 @@ class concurrent_hash_map this, h & mask, scoped_lock_traits_type::initial_rw_state(true)); - pmem::obj::transaction::run(pop, [&] { + pmem::obj::flat_transaction::run(pop, [&] { /* get full mask for new bucket */ mask = (mask << 1) | 1; assert((mask & (mask + 1)) == 0 && (h & mask) == h); @@ -2176,7 +2176,7 @@ class concurrent_hash_map this->my_size = static_cast(actual_size); auto pop = get_pool_base(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { this->tls_ptr = make_persistent(); this->on_init_size = static_cast(actual_size); @@ -2302,7 +2302,7 @@ class concurrent_hash_map auto pop = get_pool_base(); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { clear(); this->free_tls(); }); @@ -2725,9 +2725,9 @@ class concurrent_hash_map if (!result) { pool_base pop = get_pool_base(); - pmem::obj::transaction::manual tx(pop); + pmem::obj::flat_transaction::manual tx(pop); acc->second = std::forward(obj); - pmem::obj::transaction::commit(); + pmem::obj::flat_transaction::commit(); } return result; @@ -2753,9 +2753,9 @@ class concurrent_hash_map if (!result) { pool_base pop = get_pool_base(); - pmem::obj::transaction::manual tx(pop); + pmem::obj::flat_transaction::manual tx(pop); acc->second = std::forward(obj); - pmem::obj::transaction::commit(); + pmem::obj::flat_transaction::commit(); } return result; @@ -2788,9 +2788,9 @@ class concurrent_hash_map if (!result) { pool_base pop = get_pool_base(); - pmem::obj::transaction::manual tx(pop); + pmem::obj::flat_transaction::manual tx(pop); acc->second = std::forward(obj); - pmem::obj::transaction::commit(); + pmem::obj::flat_transaction::commit(); } return result; @@ -3262,7 +3262,7 @@ restart : { /* Only one thread can delete it due to write lock on the bucket */ - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { *p = del->next; delete_node(del); @@ -3336,7 +3336,7 @@ concurrent_hash_map::clear() pool_base pop = get_pool_base(); { /* transaction scope */ - transaction::manual tx(pop); + flat_transaction::manual tx(pop); assert(this->tls_ptr != nullptr); this->tls_ptr->clear(); @@ -3358,13 +3358,13 @@ concurrent_hash_map::clear() * transaction we must make sure that mask and size * changes are transactional */ - transaction::snapshot((size_t *)&this->my_mask); - transaction::snapshot((size_t *)&this->my_size); + flat_transaction::snapshot((size_t *)&this->my_mask); + flat_transaction::snapshot((size_t *)&this->my_size); mask().store(embedded_buckets - 1, std::memory_order_relaxed); this->my_size = 0; - transaction::commit(); + flat_transaction::commit(); } } diff --git a/include/libpmemobj++/container/detail/concurrent_skip_list_impl.hpp b/include/libpmemobj++/container/detail/concurrent_skip_list_impl.hpp index b8228a0e43..671d84e084 100644 --- a/include/libpmemobj++/container/detail/concurrent_skip_list_impl.hpp +++ b/include/libpmemobj++/container/detail/concurrent_skip_list_impl.hpp @@ -215,7 +215,7 @@ class skip_list_node { assert(level < height()); assert(pmemobj_tx_stage() == TX_STAGE_WORK); auto &node = get_next(level); - obj::transaction::snapshot(&node); + obj::flat_transaction::snapshot(&node); node.store(next, std::memory_order_release); } @@ -753,7 +753,7 @@ class concurrent_skip_list { return; auto pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { clear(); delete_dummy_head(); }); @@ -799,7 +799,7 @@ class concurrent_skip_list { return *this; obj::pool_base pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { using pocca_t = typename node_allocator_traits:: propagate_on_container_copy_assignment; clear(); @@ -841,7 +841,7 @@ class concurrent_skip_list { return *this; obj::pool_base pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { using pocma_t = typename node_allocator_traits:: propagate_on_container_move_assignment; clear(); @@ -878,7 +878,7 @@ class concurrent_skip_list { operator=(std::initializer_list il) { obj::pool_base pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { clear(); for (auto it = il.begin(); it != il.end(); ++it) internal_unsafe_emplace(*it); @@ -1288,7 +1288,7 @@ class concurrent_skip_list { obj::pool_base pop = get_pool_base(); auto &size_diff = tls_data.local().size_diff; - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { while (first != last) { first = internal_erase(first, size_diff); } @@ -1981,7 +1981,7 @@ class concurrent_skip_list { persistent_node_ptr current = dummy_head->next(0); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { while (current) { assert(current->height() > 0); persistent_node_ptr next = current->next(0); @@ -1996,7 +1996,7 @@ class concurrent_skip_list { on_init_size = 0; tls_data.clear(); - obj::transaction::snapshot((size_t *)&_size); + obj::flat_transaction::snapshot((size_t *)&_size); _size = 0; }); } @@ -2129,7 +2129,7 @@ class concurrent_skip_list { swap(concurrent_skip_list &other) { obj::pool_base pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { using pocs_t = typename node_allocator_traits:: propagate_on_container_swap; allocator_swap(_node_allocator, other._node_allocator, @@ -2139,8 +2139,9 @@ class concurrent_skip_list { std::swap(dummy_head, other.dummy_head); on_init_size.swap(other.on_init_size); - obj::transaction::snapshot((size_t *)&_size); - obj::transaction::snapshot((size_t *)&(other._size)); + obj::flat_transaction::snapshot((size_t *)&_size); + obj::flat_transaction::snapshot( + (size_t *)&(other._size)); _size = other._size.exchange(_size, std::memory_order_relaxed); }); @@ -2506,7 +2507,7 @@ class concurrent_skip_list { tls_entry_type &tls_entry = tls_data.local(); obj::pool_base pop = get_pool_base(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { assert(tls_entry.ptr == nullptr); tls_entry.ptr = create_node(std::forward(args)...); @@ -2537,7 +2538,7 @@ class concurrent_skip_list { assert(tls_entry.ptr != nullptr); assert(tls_entry.insert_stage == not_started); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { --tls_entry.size_diff; delete_node(tls_entry.ptr); tls_entry.ptr = nullptr; @@ -2605,7 +2606,7 @@ class concurrent_skip_list { -> persistent_node_ptr & { obj::pool_base pop = get_pool_base(); - obj::transaction::manual tx(pop); + obj::flat_transaction::manual tx(pop); tls_entry.ptr = create_node( std::forward_as_tuple( height, next_nodes.data()), @@ -2614,7 +2615,7 @@ class concurrent_skip_list { ++(tls_entry.size_diff); tls_entry.insert_stage = in_progress; - obj::transaction::commit(); + obj::flat_transaction::commit(); assert(tls_entry.ptr != nullptr); return tls_entry.ptr; @@ -2860,14 +2861,14 @@ class concurrent_skip_list { std::pair extract_result(nullptr, nullptr); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { extract_result = internal_extract(pos); /* Make sure that node was extracted */ assert(extract_result.first != nullptr); delete_node(extract_result.first); --size_diff; - obj::transaction::snapshot((size_type *)&_size); + obj::flat_transaction::snapshot((size_type *)&_size); --_size; }); @@ -2969,7 +2970,7 @@ class concurrent_skip_list { * there can be an outer transaction we must make sure that size * change is transactional */ - obj::transaction::snapshot((size_type *)&_size); + obj::flat_transaction::snapshot((size_type *)&_size); _size = sz; assert(std::is_sorted( this->begin(), this->end(), @@ -3155,7 +3156,7 @@ class concurrent_skip_list { if (tls_entry.insert_stage == in_progress) { complete_insert(tls_entry); } else { - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { --(tls_entry.size_diff); delete_node(node); node = nullptr; @@ -3172,7 +3173,7 @@ class concurrent_skip_list { assert(last_run_size >= 0 || on_init_size > static_cast(std::abs(last_run_size))); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { tls_data.clear(); on_init_size += static_cast(last_run_size); }); diff --git a/include/libpmemobj++/container/segment_vector.hpp b/include/libpmemobj++/container/segment_vector.hpp index e14081dda4..cae406588b 100644 --- a/include/libpmemobj++/container/segment_vector.hpp +++ b/include/libpmemobj++/container/segment_vector.hpp @@ -1071,7 +1071,7 @@ segment_vector::assign(size_type count, const_reference value) throw std::length_error("Assignable range exceeds max size."); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (count > capacity()) internal_reserve(count); else if (count < size()) @@ -1128,7 +1128,7 @@ segment_vector::assign(InputIt first, InputIt last) throw std::length_error("Assignable range exceeds max size."); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (count > capacity()) internal_reserve(count); else if (count < size()) @@ -1231,7 +1231,7 @@ segment_vector::assign(segment_vector &&other) return; pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { _data = std::move(other._data); _segments_used = other._segments_used; other._segments_used = 0; @@ -1767,7 +1767,7 @@ segment_vector::reserve(size_type capacity_new) return; pool_base pb = get_pool(); - transaction::run(pb, [&] { internal_reserve(capacity_new); }); + flat_transaction::run(pb, [&] { internal_reserve(capacity_new); }); } /** @@ -1805,7 +1805,7 @@ segment_vector::shrink_to_fit() return; pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { for (size_type i = new_last + 1; i < _segments_used; ++i) _data[i].free_data(); _segments_used = new_last + 1; @@ -1829,7 +1829,7 @@ void segment_vector::clear() { pool_base pb = get_pool(); - transaction::run(pb, [&] { shrink(0); }); + flat_transaction::run(pb, [&] { shrink(0); }); assert(segment_capacity_validation()); } @@ -1850,7 +1850,7 @@ void segment_vector::free_data() { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { for (size_type i = 0; i < _segments_used; ++i) _data[i].free_data(); _segments_used = 0; @@ -1917,7 +1917,7 @@ segment_vector::insert(const_iterator pos, T &&value) size_type idx = static_cast(pos - cbegin()); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { insert_gap(idx, 1); get(idx) = std::move(value); }); @@ -1959,7 +1959,7 @@ segment_vector::insert(const_iterator pos, size_type count, size_type idx = static_cast(pos - cbegin()); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { insert_gap(idx, count); for (size_type i = idx; i < idx + count; ++i) get(i) = std::move(value); @@ -2013,7 +2013,7 @@ segment_vector::insert(const_iterator pos, InputIt first, size_type gap_size = static_cast(std::distance(first, last)); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { insert_gap(idx, gap_size); for (size_type i = idx; i < idx + gap_size; ++i, ++first) get(i) = *first; @@ -2092,7 +2092,7 @@ segment_vector::emplace(const_iterator pos, Args &&... args) size_type idx = static_cast(pos - cbegin()); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { detail::temp_value(args)...))> tmp(std::forward(args)...); @@ -2135,7 +2135,7 @@ segment_vector::emplace_back(Args &&... args) assert(size() < max_size()); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (size() == capacity()) internal_reserve(capacity() + 1); @@ -2209,7 +2209,7 @@ segment_vector::erase(const_iterator first, const_iterator last) return iterator(this, idx); pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { size_type _size = size(); if (!std::is_trivially_destructible::value || @@ -2311,7 +2311,7 @@ segment_vector::pop_back() return; pool_base pb = get_pool(); - transaction::run(pb, [&] { shrink(size() - 1); }); + flat_transaction::run(pb, [&] { shrink(size() - 1); }); assert(segment_capacity_validation()); } @@ -2343,7 +2343,7 @@ void segment_vector::resize(size_type count) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { size_type _size = size(); if (count < _size) shrink(count); @@ -2385,7 +2385,7 @@ void segment_vector::resize(size_type count, const value_type &value) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { size_type _size = size(); if (count < _size) shrink(count); @@ -2406,7 +2406,7 @@ void segment_vector::swap(segment_vector &other) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { _data.swap(other._data); std::swap(_segments_used, other._segments_used); }); diff --git a/include/libpmemobj++/container/vector.hpp b/include/libpmemobj++/container/vector.hpp index 6077e20a16..c4fd8bc604 100644 --- a/include/libpmemobj++/container/vector.hpp +++ b/include/libpmemobj++/container/vector.hpp @@ -632,7 +632,7 @@ vector::assign(size_type count, const_reference value) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (count <= capacity()) { /* * Reallocation is not needed. First, replace old @@ -692,7 +692,7 @@ vector::assign(InputIt first, InputIt last) size_type size_new = static_cast(std::distance(first, last)); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (size_new <= capacity()) { /* * Reallocation is not needed. First, replace old @@ -790,7 +790,7 @@ vector::assign(vector &&other) pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { dealloc(); _data = other._data; @@ -1387,7 +1387,7 @@ vector::reserve(size_type capacity_new) return; pool_base pb = get_pool(); - transaction::run(pb, [&] { realloc(capacity_new); }); + flat_transaction::run(pb, [&] { realloc(capacity_new); }); } /** @@ -1423,7 +1423,7 @@ vector::shrink_to_fit() return; pool_base pb = get_pool(); - transaction::run(pb, [&] { realloc(capacity_new); }); + flat_transaction::run(pb, [&] { realloc(capacity_new); }); } /** @@ -1439,7 +1439,7 @@ void vector::clear() { pool_base pb = get_pool(); - transaction::run(pb, [&] { shrink(0); }); + flat_transaction::run(pb, [&] { shrink(0); }); } /** @@ -1462,7 +1462,7 @@ vector::free_data() return; pool_base pb = get_pool(); - transaction::run(pb, [&] { dealloc(); }); + flat_transaction::run(pb, [&] { dealloc(); }); } /** @@ -1528,7 +1528,7 @@ vector::insert(const_iterator pos, value_type &&value) size_type idx = static_cast(std::distance(cbegin(), pos)); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { internal_insert(idx, std::make_move_iterator(&value), std::make_move_iterator(&value + 1)); }); @@ -1572,7 +1572,7 @@ vector::insert(const_iterator pos, size_type count, const value_type &value) size_type idx = static_cast(std::distance(cbegin(), pos)); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { internal_insert( idx, single_element_iterator(&value, 0), single_element_iterator(&value, count)); @@ -1626,7 +1626,7 @@ vector::insert(const_iterator pos, InputIt first, InputIt last) size_type idx = static_cast(std::distance(cbegin(), pos)); - transaction::run(pb, [&] { internal_insert(idx, first, last); }); + flat_transaction::run(pb, [&] { internal_insert(idx, first, last); }); return iterator(&_data[static_cast(idx)]); } @@ -1702,7 +1702,7 @@ vector::emplace(const_iterator pos, Args &&... args) size_type idx = static_cast(std::distance(cbegin(), pos)); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { /* * args might be a reference to underlying array element. This * reference can be invalidated after internal_insert() call. @@ -1755,7 +1755,7 @@ vector::emplace_back(Args &&... args) */ pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (_size == _capacity) { realloc(get_recommended_capacity(_size + 1)); } else { @@ -1829,7 +1829,7 @@ vector::erase(const_iterator first, const_iterator last) pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (!std::is_trivially_destructible::value || idx + count < _size) add_data_to_tx(idx, _size - idx); @@ -1910,7 +1910,7 @@ vector::pop_back() return; pool_base pb = get_pool(); - transaction::run(pb, [&] { shrink(size() - 1); }); + flat_transaction::run(pb, [&] { shrink(size() - 1); }); } /** @@ -1934,7 +1934,7 @@ void vector::resize(size_type count) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (count <= _size) shrink(count); else { @@ -1970,7 +1970,7 @@ vector::resize(size_type count, const value_type &value) return; pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { if (count <= _size) shrink(count); else { @@ -1989,7 +1989,7 @@ void vector::swap(vector &other) { pool_base pb = get_pool(); - transaction::run(pb, [&] { + flat_transaction::run(pb, [&] { std::swap(this->_data, other._data); std::swap(this->_size, other._size); std::swap(this->_capacity, other._capacity); diff --git a/include/libpmemobj++/detail/enumerable_thread_specific.hpp b/include/libpmemobj++/detail/enumerable_thread_specific.hpp index fede479a9b..b131d92548 100644 --- a/include/libpmemobj++/detail/enumerable_thread_specific.hpp +++ b/include/libpmemobj++/detail/enumerable_thread_specific.hpp @@ -346,7 +346,7 @@ enumerable_thread_specific::clear() { auto pop = get_pool(); - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { _storage_size.get_rw() = 0; _storage.clear(); }); diff --git a/include/libpmemobj++/detail/volatile_state.hpp b/include/libpmemobj++/detail/volatile_state.hpp index 6ba525360d..7abc9f8f37 100644 --- a/include/libpmemobj++/detail/volatile_state.hpp +++ b/include/libpmemobj++/detail/volatile_state.hpp @@ -107,8 +107,8 @@ class volatile_state { destroy(const PMEMoid &oid) { if (pmemobj_tx_stage() == TX_STAGE_WORK) { - obj::transaction::register_callback( - obj::transaction::stage::oncommit, [oid] { + obj::flat_transaction::register_callback( + obj::flat_transaction::stage::oncommit, [oid] { std::unique_lock lock( get_rwlock()); get_map().erase(oid); diff --git a/include/libpmemobj++/experimental/inline_string.hpp b/include/libpmemobj++/experimental/inline_string.hpp index cd37b4b960..064325ff03 100644 --- a/include/libpmemobj++/experimental/inline_string.hpp +++ b/include/libpmemobj++/experimental/inline_string.hpp @@ -379,7 +379,7 @@ basic_inline_string::assign(basic_string_view rhs) auto initialized_mem = (std::min)(rhs.size(), size()) + 1 /* sizeof('\0') */; - obj::transaction::run(pop, [&] { + obj::flat_transaction::run(pop, [&] { detail::conditional_add_to_tx(data(), initialized_mem); if (rhs.size() > size()) diff --git a/include/libpmemobj++/experimental/radix_tree.hpp b/include/libpmemobj++/experimental/radix_tree.hpp index 30c1ef1f4f..11700de4ea 100644 --- a/include/libpmemobj++/experimental/radix_tree.hpp +++ b/include/libpmemobj++/experimental/radix_tree.hpp @@ -796,7 +796,7 @@ radix_tree::operator=(const radix_tree &other) auto pop = pool_by_vptr(this); if (this != &other) { - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { clear(); this->root = nullptr; @@ -827,7 +827,7 @@ radix_tree::operator=(radix_tree &&other) auto pop = pool_by_vptr(this); if (this != &other) { - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { clear(); this->root = other.root; @@ -928,7 +928,7 @@ radix_tree::swap(radix_tree &rhs) { auto pop = pool_by_vptr(this); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { this->size_.swap(rhs.size_); this->root.swap(rhs.root); }); @@ -1149,7 +1149,8 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) auto pop = pool_base(pmemobj_pool_by_ptr(this)); if (!root) { - transaction::run(pop, [&] { this->root = make_leaf(nullptr); }); + flat_transaction::run(pop, + [&] { this->root = make_leaf(nullptr); }); return {iterator(root.get_leaf(), &root), true}; } @@ -1184,7 +1185,7 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) if (!n) { assert(diff < (std::min)(leaf_key.size(), key.size())); - transaction::run(pop, [&] { *slot = make_leaf(prev); }); + flat_transaction::run(pop, [&] { *slot = make_leaf(prev); }); return {iterator(slot->get_leaf(), &root), true}; } @@ -1194,7 +1195,7 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) if (!n.is_leaf() && path_length_equal(key.size(), n)) { assert(!n->embedded_entry); - transaction::run( + flat_transaction::run( pop, [&] { n->embedded_entry = make_leaf(n); }); return {iterator(n->embedded_entry.get_leaf(), &root), @@ -1204,7 +1205,7 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) /* Path length from root to n is longer than key.size(). * We have to allocate new internal node above n. */ tagged_node_ptr node; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { node = make_persistent( parent_ref(n), diff, bitn_t(FIRST_NIB)); node->embedded_entry = make_leaf(node); @@ -1222,7 +1223,7 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) /* Leaf key is a prefix of the new key. We need to convert leaf * to a node. */ tagged_node_ptr node; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { /* We have to add new node at the edge from parent to n */ node = make_persistent( @@ -1247,7 +1248,7 @@ radix_tree::internal_emplace(const K &k, F &&make_leaf) * compressed and we have to "break" this compression and add a new * node. */ tagged_node_ptr node; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { node = make_persistent(parent_ref(n), diff, sh); node->child[slice_index(leaf_key[diff], sh)] = n; @@ -1337,7 +1338,7 @@ radix_tree::emplace(Args &&... args) auto pop = pool_base(pmemobj_pool_by_ptr(this)); std::pair ret; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { auto leaf_ = leaf::make(nullptr, std::forward(args)...); auto make_leaf = [&](tagged_node_ptr parent) { leaf_->parent = parent; @@ -1804,7 +1805,7 @@ radix_tree::erase(const_iterator pos) { auto pop = pool_base(pmemobj_pool_by_ptr(this)); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { auto *leaf = pos.leaf_; auto parent = leaf->parent; @@ -1883,7 +1884,7 @@ radix_tree::erase(const_iterator first, { auto pop = pool_base(pmemobj_pool_by_ptr(this)); - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { while (first != last) first = erase(first); }); @@ -2791,7 +2792,7 @@ radix_tree::radix_tree_iterator::assign_val( auto pop = pool_base(pmemobj_pool_by_ptr(leaf_)); if (rhs.size() <= leaf_->value().capacity()) { - transaction::run(pop, [&] { leaf_->value() = rhs; }); + flat_transaction::run(pop, [&] { leaf_->value() = rhs; }); } else { tagged_node_ptr *slot; @@ -2805,7 +2806,7 @@ radix_tree::radix_tree_iterator::assign_val( auto old_leaf = leaf_; - transaction::run(pop, [&] { + flat_transaction::run(pop, [&] { *slot = leaf::make_key_args(old_leaf->parent, old_leaf->key(), rhs); delete_persistent(old_leaf); @@ -2829,7 +2830,8 @@ radix_tree::radix_tree_iterator::assign_val( { auto pop = pool_base(pmemobj_pool_by_ptr(leaf_)); - transaction::run(pop, [&] { leaf_->value() = std::forward(rhs); }); + flat_transaction::run(pop, + [&] { leaf_->value() = std::forward(rhs); }); } template From 7be3fb7e34c0a153f71f115325a315028d3c3879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Chor=C4=85=C5=BCewicz?= Date: Mon, 12 Oct 2020 14:32:31 +0200 Subject: [PATCH 3/4] tests: enable segment vector oom tests They will work after introducing flat_transaction. --- tests/CMakeLists.txt | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f296d581de..698c837054 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -661,13 +661,8 @@ if(TEST_SEGMENT_VECTOR_ARRAY_EXPSIZE) build_test_ext(NAME segment_vector_array_expsize_ctor_exceptions_notx SRC_FILES vector_ctor_exceptions_notx/vector_ctor_exceptions_notx.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_ARRAY_EXPSIZE) add_test_generic(NAME segment_vector_array_expsize_ctor_exceptions_notx TRACERS none memcheck) - #[[ XXX: - When we throwing exceptions in segment vector constructor, destructors is called for all constructed element according to standard. - In this way for segment with non-zero capacity in the underlying vectors destructor free_data() methods is called. - The free_data() run nested transaction which is failed to run when internally calls pmemobj_tx_begin(pool.handle(), nullptr, TX_PARAM_NONE). - ]] - # build_test_ext(NAME segment_vector_array_expsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_ARRAY_EXPSIZE) - # add_test_generic(NAME segment_vector_array_expsize_ctor_exceptions_oom TRACERS none memcheck pmemcheck) + build_test_ext(NAME segment_vector_array_expsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_ARRAY_EXPSIZE) + add_test_generic(NAME segment_vector_array_expsize_ctor_exceptions_oom TRACERS none memcheck pmemcheck) build_test_ext(NAME segment_vector_array_expsize_ctor_move SRC_FILES vector_ctor_move/vector_ctor_move.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_ARRAY_EXPSIZE) add_test_generic(NAME segment_vector_array_expsize_ctor_move TRACERS none memcheck pmemcheck) @@ -734,13 +729,8 @@ if(TEST_SEGMENT_VECTOR_VECTOR_EXPSIZE) build_test_ext(NAME segment_vector_vector_expsize_ctor_exceptions_notx SRC_FILES vector_ctor_exceptions_notx/vector_ctor_exceptions_notx.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_EXPSIZE) add_test_generic(NAME segment_vector_vector_expsize_ctor_exceptions_notx TRACERS none memcheck) - #[[ XXX: - When we throwing exceptions in segment vector constructor, destructors is called for all constructed element according to standard. - In this way for segment with non-zero capacity in the underlying vectors destructor free_data() methods is called. - The free_data() run nested transaction which is failed to run when internally calls pmemobj_tx_begin(pool.handle(), nullptr, TX_PARAM_NONE). - ]] - # build_test_ext(NAME segment_vector_vector_expsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_EXPSIZE) - # add_test_generic(NAME segment_vector_vector_expsize_ctor_exceptions_oom TRACERS none memcheck pmemcheck) + build_test_ext(NAME segment_vector_vector_expsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_EXPSIZE) + add_test_generic(NAME segment_vector_vector_expsize_ctor_exceptions_oom TRACERS none memcheck pmemcheck) build_test_ext(NAME segment_vector_vector_expsize_ctor_move SRC_FILES vector_ctor_move/vector_ctor_move.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_EXPSIZE) add_test_generic(NAME segment_vector_vector_expsize_ctor_move TRACERS none memcheck pmemcheck) @@ -807,13 +797,8 @@ if(TEST_SEGMENT_VECTOR_VECTOR_FIXEDSIZE) build_test_ext(NAME segment_vector_vector_fixedsize_ctor_exceptions_notx SRC_FILES vector_ctor_exceptions_notx/vector_ctor_exceptions_notx.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_FIXEDSIZE) add_test_generic(NAME segment_vector_vector_fixedsize_ctor_exceptions_notx TRACERS none memcheck) - #[[ XXX: - When we throwing exceptions in segment vector constructor, destructors is called for all constructed element according to standard. - In this way for segment with non-zero capacity in the underlying vectors destructor free_data() methods is called. - The free_data() run nested transaction which is failed to run when internally calls pmemobj_tx_begin(pool.handle(), nullptr, TX_PARAM_NONE). - ]] - # build_test_ext(NAME segment_vector_vector_fixedsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_FIXEDSIZE) - # add_test_generic(NAME segment_vector_vector_fixedsize_ctor_exceptions_oom TRACERS none memcheck pmemcheck) + build_test_ext(NAME segment_vector_vector_fixedsize_ctor_exceptions_oom SRC_FILES vector_ctor_exceptions_oom/vector_ctor_exceptions_oom.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_FIXEDSIZE) + add_test_generic(NAME segment_vector_vector_fixedsize_ctor_exceptions_oom TRACERS none memcheck) # XXX: pmemcheck timeouts build_test_ext(NAME segment_vector_vector_fixedsize_ctor_move SRC_FILES vector_ctor_move/vector_ctor_move.cpp BUILD_OPTIONS -DSEGMENT_VECTOR_VECTOR_FIXEDSIZE) add_test_generic(NAME segment_vector_vector_fixedsize_ctor_move TRACERS none memcheck pmemcheck) From 98c3f96b102fc905b846aa19fdf23dc418aa3757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Chor=C4=85=C5=BCewicz?= Date: Mon, 12 Oct 2020 15:23:22 +0200 Subject: [PATCH 4/4] readme: add information about introducing flat_transaction and changing containers' transactional methods behavior. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index aff7ab1a97..c97d68e6a5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ More implementation details can be found in [include/libpmemobj++/README.md](inc Latest releases can be found on the ["releases" tab](https://github.com/pmem/libpmemobj-cpp/releases). Up-to-date support/maintenance status of branches/releases is available on [pmem.io](https://pmem.io/libpmemobj-cpp). +# Compatibility note # +In libpmemobj 1.12 we introduced a new transaction handler type: [pmem::obj::flat_transaction](https://pmem.io/libpmemobj-cpp/master/doxygen/classpmem_1_1obj_1_1flat__transaction.html). By defining LIBPMEMOBJ_CPP_USE_FLAT_TRANSACTION you can make pmem::obj::transaction to be an alias to pmem::obj::flat_transaction. In 1.12 we have also changed the default behavior of containers' transactional methods. Now, in case of any failure within such method, the outer transaction (if any) will not be immediately aborted. Instead, an exception will be thrown, which will lead to transaction abort only if it's not caught before the outer tx scope ends. To change the behavior to the old one, you can set LIBPMEMOBJ_CPP_FLAT_TX_USE_FAILURE_RETURN macro to 0. Be aware that the old behavior can lead to segfaults in some cases (see tx_nested_struct_example in this [file](examples/transaction/transaction.cpp)). + # How to build # ## Requirements: ##