Implement support for nesting transactions

RW transactions can have RW and RO transactions nested (although
the RO transactions are really just disguised RW transactions,
because LMDB does not support nesting real RO transactions to RW
transactions).

RO transactions can not be nested (again an LMDB limitation).
This commit is contained in:
Jonas Schäfer 2019-10-26 12:07:03 +02:00
parent 3c57c6a113
commit 7ce9a82141
3 changed files with 210 additions and 4 deletions

View File

@ -152,6 +152,13 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
return ret;
}
MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn):
MDBROTransactionImpl(parent, txn)
{
}
MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, int flags)
{
MDB_txn *result;
@ -175,8 +182,7 @@ MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, i
}
MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv* parent, int flags):
MDBROTransactionImpl(parent, openRWTransaction(parent, nullptr, flags)),
d_rw_cursors()
MDBRWTransactionImpl(parent, openRWTransaction(parent, nullptr, flags))
{
}
@ -314,6 +320,22 @@ MDBRWCursor MDBRWTransactionImpl::getCursor(const MDBDbi &dbi)
return getRWCursor(dbi);
}
MDBRWTransaction MDBRWTransactionImpl::getRWTransaction()
{
MDB_txn *txn;
if (int rc = mdb_txn_begin(environment(), *this, 0, &txn)) {
throw std::runtime_error(std::string("failed to start child transaction: ")+mdb_strerror(rc));
}
// we need to increase the counter here because commit/abort on the child transaction will decrease it
environment().incRWTX();
return MDBRWTransaction(new MDBRWTransactionImpl(&environment(), txn));
}
MDBROTransaction MDBRWTransactionImpl::getROTransaction()
{
return std::move(getRWTransaction());
}
MDBROTransaction MDBEnv::getROTransaction()
{
return MDBROTransaction(new MDBROTransactionImpl(this));
@ -347,5 +369,3 @@ MDBROCursor MDBROTransactionImpl::getROCursor(const MDBDbi &dbi)
}
return MDBROCursor(d_cursors, cursor);
}

View File

@ -490,6 +490,9 @@ class MDBRWCursor;
class MDBRWTransactionImpl: public MDBROTransactionImpl
{
protected:
MDBRWTransactionImpl(MDBEnv* parent, MDB_txn* txn);
private:
static MDB_txn *openRWTransaction(MDBEnv* env, MDB_txn *parent, int flags);
@ -576,6 +579,9 @@ public:
MDBRWCursor getRWCursor(const MDBDbi&);
MDBRWCursor getCursor(const MDBDbi&);
MDBRWTransaction getRWTransaction();
MDBROTransaction getROTransaction();
};
/* "A cursor in a write-transaction can be closed before its transaction ends, and will otherwise be closed when its transaction ends"

View File

@ -147,3 +147,183 @@ TEST_CASE("transaction inheritance and moving")
CHECK(!cursor);
}
TEST_CASE("nested RW transactions", "[transactions]")
{
unlink("./tests");
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE);
/* bootstrap some data */
{
auto txn = env.getRWTransaction();
txn->put(main, "bert", "hubert");
txn->put(main, "bertt", "1975");
txn->put(main, "berthubert", "lmdb");
txn->put(main, "bert1", "one");
txn->put(main, "beru", "not");
txn->commit();
}
auto main_txn = env.getRWTransaction();
main_txn->del(main, "bertt", "1975");
MDBOutVal dummy{};
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
{
auto sub_txn = main_txn->getRWTransaction();
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
sub_txn->del(main, "berthubert", "lmdb");
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
}
/* check that subtransaction got rolled back */
CHECK(main_txn->get(main, "berthubert", dummy) == 0);
/* and that the main changes are still there */
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
{
auto sub_txn = main_txn->getRWTransaction();
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
sub_txn->del(main, "berthubert", "lmdb");
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
/* this time for real! */
sub_txn->commit();
}
CHECK(main_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
}
TEST_CASE("nesting RW -> RO", "[transactions]")
{
unlink("./tests");
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE);
/* bootstrap some data */
{
auto txn = env.getRWTransaction();
txn->put(main, "bert", "hubert");
txn->put(main, "bertt", "1975");
txn->put(main, "berthubert", "lmdb");
txn->put(main, "bert1", "one");
txn->put(main, "beru", "not");
txn->commit();
}
auto main_txn = env.getRWTransaction();
main_txn->del(main, "bertt", "1975");
MDBOutVal dummy{};
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
{
MDBROTransaction sub_txn = main_txn->getROTransaction();
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
}
/* check that subtransaction got rolled back */
CHECK(main_txn->get(main, "berthubert", dummy) == 0);
/* and that the main changes are still there */
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
{
auto sub_txn = main_txn->getRWTransaction();
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
sub_txn->del(main, "berthubert", "lmdb");
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
{
MDBROTransaction sub_sub_txn = sub_txn->getROTransaction();
CHECK(sub_sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
}
/* this time for real! */
sub_txn->commit();
}
CHECK(main_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
}
TEST_CASE("try to nest twice", "[transactions]")
{
unlink("./tests");
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE);
/* bootstrap some data */
{
auto txn = env.getRWTransaction();
txn->put(main, "bert", "hubert");
txn->put(main, "bertt", "1975");
txn->put(main, "berthubert", "lmdb");
txn->put(main, "bert1", "one");
txn->put(main, "beru", "not");
txn->commit();
}
auto main_txn = env.getRWTransaction();
main_txn->del(main, "bertt", "1975");
MDBOutVal dummy{};
CHECK(main_txn->get(main, "bertt", dummy) == MDB_NOTFOUND);
{
auto sub_txn = main_txn->getRWTransaction();
CHECK(sub_txn->get(main, "berthubert", dummy) == 0);
sub_txn->del(main, "berthubert", "lmdb");
CHECK(sub_txn->get(main, "berthubert", dummy) == MDB_NOTFOUND);
CHECK_THROWS_AS(
main_txn->getRWTransaction(),
std::runtime_error
);
}
}
TEST_CASE("transaction counter correctness for RW->RW nesting")
{
unlink("./tests");
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE);
{
auto txn = env.getRWTransaction();
auto sub_txn = txn->getRWTransaction();
}
CHECK_NOTHROW(env.getRWTransaction());
CHECK_NOTHROW(env.getROTransaction());
}
TEST_CASE("transaction counter correctness for RW->RO nesting")
{
unlink("./tests");
MDBEnv env("./tests", MDB_NOSUBDIR, 0600);
REQUIRE(1);
MDBDbi main = env.openDB("", MDB_CREATE);
{
auto txn = env.getRWTransaction();
auto sub_txn = txn->getROTransaction();
}
CHECK_NOTHROW(env.getRWTransaction());
CHECK_NOTHROW(env.getROTransaction());
}