diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 2192aa4..2f51d77 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -1,12 +1,12 @@ /*! \mainpage Getting Started * - * Everything begins with a data nase represented by the class LMDBAL::Base. + * Everything begins with a data base represented by the class LMDBAL::Base. * It repesents a collection of key-value storages that are going to be stored in a sigle data base directory. * To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine. * * LMDBAL::Base creates or opens existing directory with the given name in the location acquired with * QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - * so, the file system destination of your data would depend on the + * so, the file system destination of your data depends on the * QCoreApplication configuration of your app. * * After you have created a LMDBAL::Base you probably want to obtain storage handlers. @@ -15,10 +15,10 @@ * std::map * to speed up the access. * - * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&, bool) or LMDBAL::Base::addCache(const std::string& name). + * You can obtain handlers by calling LMDBAL::Base::addStorage() or LMDBAL::Base::addCache(). * Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them. * You are not obliged to save those handlers, - * you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&) + * you can obtain them at any time later using methods LMDBAL::Base::getStorage() or LMDBAL::Base::getCache() * calling them with the same template types and names. * * After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open(). diff --git a/src/base.cpp b/src/base.cpp index 604f9da..81f9bdb 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -216,6 +216,14 @@ void LMDBAL::Base::drop() { /** * \brief Begins read-only transaction * + * This is the legitimate way to retrieve LMDBAL::Transaction. + * LMDBAL::Transaction is considered runnig right after creation by this method. + * You can terminate transaction manually calling LMDBAL::Transaction::terminate + * but it's not required, because transaction will be terminated automatically + * (if it was not terminated manually) upon the call of the destructor. + * + * You can not use termitated transaction any more. + * * \returns read-only transaction * * \exception LMDBAL::Closed - thrown if the database is closed @@ -229,6 +237,16 @@ LMDBAL::Transaction LMDBAL::Base::beginReadOnlyTransaction() const { /** * \brief Begins writable transaction * + * This is the legitimate way to retrieve LMDBAL::WriteTransaction. + * LMDBAL::WriteTransaction is considered runnig right after creation by this method. + * You can commit all the changes made by this transaction calling LMDBAL::WriteTransaction::commit. + * You can cancel any changes made bu this transaction calling LMDBAL::WriteTransaction::abort + * (or LMDBAL::Transaction::terminate which LMDBAL::WriteTransaction inherits), + * but it's not required, because transaction will be aborted automatically + * (if it was not terminated (committed OR aborted) manually) upon the call of the destructor. + * + * You can not use termitated transaction any more. + * * \returns writable transaction * * \exception LMDBAL::Closed - thrown if the database is closed diff --git a/src/cursor.hpp b/src/cursor.hpp index 00fc346..cbc293f 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -186,7 +186,7 @@ void LMDBAL::Cursor::renew () const { * * This function does nothing if the cursor is closed * - * \param[in] txn a transaction you wish this cursor to be bound to + * \param[in] transaction - a transaction you wish this cursor to be bound to * * \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database * \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb diff --git a/src/storage.cpp b/src/storage.cpp index 26614dd..f1258fa 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -439,4 +439,10 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) { void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const { UNUSED(txn);} +/** + * \brief A method where database additionally handles drop + * + * It's a protected method that is called to optimise drop process + * after the transaction is commited. Used just for optimisations. + */ void LMDBAL::iStorage::handleDrop() {} diff --git a/src/transaction.cpp b/src/transaction.cpp index 47744b4..3632ad6 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -1,17 +1,42 @@ #include "transaction.h" +/** + * \class LMDBAL::Transaction + * \brief Public read only transaction + * + * This class provides read only transactions you can use + * to speed to your queries keeping them thread safe. + * LMDBAL::Transaction is NOT COPYABLE but MOVABLE. + * Transaction can be in one of two states: active or terminated. + * The way to receive an active LMDBAL::Transaction is to call LMDBAL::Base::beginReadOnlyTransaction. + * + * Active transactions become terminated upon the call of LMDBAL::Transaction::terminate. + * Active transactions automaticaly terminate themselves upon destruction. + * + * You CAN NOT use inactive transactions for any query. + */ + +/** + * \brief Constructs inactive transaction + */ LMDBAL::Transaction::Transaction(): txn(nullptr), active(false), parent(nullptr) {} +/** + * \brief Constructs an active transaction + */ LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) : txn(txn), active(true), parent(parent) {} +/** + * \brief Moves transaction to a new object + */ LMDBAL::Transaction::Transaction(Transaction&& other): txn(other.txn), active(other.active), @@ -20,10 +45,16 @@ LMDBAL::Transaction::Transaction(Transaction&& other): other.active = false; } +/** + * \brief Destroys transaction + */ LMDBAL::Transaction::~Transaction() { terminate(); } +/** + * \brief Move-assigns transaction to the new object + */ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { terminate(); @@ -36,6 +67,11 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) { return *this; } +/** + * \brief Terminates transaction if it was active + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::Transaction::terminate() { if (active) { parent->abortTransaction(txn); @@ -43,26 +79,73 @@ void LMDBAL::Transaction::terminate() { } } +/** + * \brief Returns transaction states + * + * \returns true if the transaction is active, false otherwise + */ bool LMDBAL::Transaction::isActive() const { return active; //todo may be it's better if I query it from DB? } + +/** + * \class LMDBAL::WriteTransaction + * \brief Public writable transaction + * + * This class provides writable transactions you can use + * to speed to your queries and modifications keeping them thread safe. + * LMDBAL::WriteTransaction is NOT COPYABLE but MOVABLE. + * Transaction can be in one of two states: active or terminated. + * The way to receive an active LMDBAL::WriteTransaction is to call LMDBAL::Base::beginTransaction. + * You can use LMDBAL::WriteTransaction for everything instead of LMDBAL::Transaction + * + * Active transactions become terminated upon the call of + * LMDBAL::WriteTransaction::abort or LMDBAL::WriteTransaction::commit. + * Calling LMDBAL::Transaction::terminate on LMDBAL::WriteTransaction + * is exactly the same as calling LMDBAL::WriteTransaction::abort. + * + * Active transactions automaticaly terminate themselves upon destruction. + * For LMDBAL::WriteTransaction default behaviour upon destruction is to abort. + * + * You CAN NOT use inactive transactions for any query. + */ + +/** + * \brief Constructs active write transaction + */ LMDBAL::WriteTransaction::WriteTransaction(TransactionID txn, Base* parent): Transaction(txn, parent) {} +/** + * \brief Constructs inactive write transaction + */ LMDBAL::WriteTransaction::WriteTransaction(): Transaction() {} +/** + * \brief Moves transaction to the new object + */ LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other): Transaction(std::move(other)) {} +/** + * \brief Aborts transaction cancelling all changes + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::WriteTransaction::abort() { terminate(); } +/** + * \brief Commits transaction submitting all changes + * + * Transaction becomes terminated after calling this method + */ void LMDBAL::WriteTransaction::commit() { if (active) { const_cast(parent)->commitTransaction(txn); diff --git a/src/transaction.h b/src/transaction.h index 77ce26a..43bb69e 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -23,9 +23,9 @@ protected: Transaction(TransactionID txn, const Base* parent); protected: - TransactionID txn; - bool active; - const Base* parent; + TransactionID txn; /**<\brief Transaction inner handler*/ + bool active; /**<\brief Transaction state*/ + const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/ }; class WriteTransaction : public Transaction { diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp index b86fd2f..6092dfb 100644 --- a/test/cachetransaction.cpp +++ b/test/cachetransaction.cpp @@ -229,3 +229,51 @@ TEST_F(CacheTransactionsTest, ConcurentModification) { EXPECT_EQ(c1->getRecord(5), -46); } +TEST_F(CacheTransactionsTest, RAIIResourceFree) { + EXPECT_EQ(db->ready(), true); + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining child transaction" << std::endl; + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the parent transaction value" << std::endl; + EXPECT_FALSE(c1->checkRecord(221, txn2)); + + std::cout << "performing modification from the child thread" << std::endl; + c1->addRecord(221, 14, txn2); + + std::cout << "commiting child transaction" << std::endl; + txn2.commit(); + + std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining parent transaction" << std::endl; + { + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + std::cout << "parent thread woke up" << std::endl; + + std::cout << "adding value from parent thread" << std::endl; + c1->addRecord(221, 320, txn1); + + std::cout << "checking value from parent thread using transaction" << std::endl; + EXPECT_EQ(c1->getRecord(221, txn1), 320); + + std::cout << "checking value independently from parent thread" << std::endl; + EXPECT_FALSE(c1->checkRecord(221)); + + std::cout << "implicitly aborting transaction by leaving the scope" << std::endl; + } + + std::cout << "child thread should resume after this line" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking the final result" << std::endl; + EXPECT_EQ(c1->getRecord(221), 14); +} + diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp index 1d93946..ad21846 100644 --- a/test/storagetransaction.cpp +++ b/test/storagetransaction.cpp @@ -14,7 +14,7 @@ protected: ~StorageTransactionsTest() {} - int waitForChildFork(int pid) { + int waitForChildFork(int pid) { int status; if (0 > waitpid(pid, &status, 0)) { std::cerr << "[----------] Waitpid error!" << std::endl; @@ -22,9 +22,9 @@ protected: } if (WIFEXITED(status)) { const int exit_status = WEXITSTATUS(status); - if (exit_status != 0) { + if (exit_status != 0) std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl; - } + return exit_status; } else { std::cerr << "[----------] Non-normal exit from child!" << std::endl; @@ -224,6 +224,54 @@ TEST_F(StorageTransactionsTest, ConcurentModification) { ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems } - std::cout << "checking final result" << std::endl; + std::cout << "checking the final result" << std::endl; EXPECT_EQ(t1->getRecord(5), -46); } + +TEST_F(StorageTransactionsTest, RAIIResourceFree) { + EXPECT_EQ(db->ready(), true); + + int pid = fork(); + if (pid == 0) { // I am the child + std::cout << "beggining child transaction" << std::endl; + LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause + //and wait for the first transaction to get finished + std::cout << "checking result of the parent transaction value" << std::endl; + EXPECT_FALSE(t1->checkRecord(221, txn2)); + + std::cout << "performing modification from the child thread" << std::endl; + t1->addRecord(221, 14, txn2); + + std::cout << "commiting child transaction" << std::endl; + txn2.commit(); + + std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl; + exit(testing::Test::HasFailure()); + } else { // I am the parent + std::cout << "beggining parent transaction" << std::endl; + { + LMDBAL::WriteTransaction txn1 = db->beginTransaction(); + + std::cout << "putting parent thread to sleep for 5 ms" << std::endl; + usleep(5); + std::cout << "parent thread woke up" << std::endl; + + std::cout << "adding value from parent thread" << std::endl; + t1->addRecord(221, 320, txn1); + + std::cout << "checking value from parent thread using transaction" << std::endl; + EXPECT_EQ(t1->getRecord(221, txn1), 320); + + std::cout << "checking value independently from parent thread" << std::endl; + EXPECT_FALSE(t1->checkRecord(221)); + + std::cout << "implicitly aborting transaction by leaving the scope" << std::endl; + } + + std::cout << "child thread should resume after this line" << std::endl; + ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems + } + + std::cout << "checking the final result" << std::endl; + EXPECT_EQ(t1->getRecord(221), 14); +}