Skip to content

Commit

Permalink
Workaround for "Duplicate RW transaction" LMDB exception.
Browse files Browse the repository at this point in the history
Although LMDB supports concurrent RO and RW transactions (but no more
than one writer), the existing lmdb access code here seems to be written
assuming an LMDB RW transaction is not allowed to coexist with any other
transaction, even RO ones.

The logic to decide whether a transaction would be RO or RW was relying
upon the way the database was opened at mdb_dbi_open() time, which turns
out to be (almost always) read-write, despite the kind of the upcoming
requests.

We now pass a boolean further in MDBEnv::openDB() to specify the intent
of the operation, rather than trusting the shared environment only.

This allows us to build more MDBROTransaction objects where
MDBRWTransaction objects would have been used previously The existing
test would raise an exception, should there be existing RO transactions.
  • Loading branch information
miodvallat committed Jan 27, 2025
1 parent 0ce41bc commit e900845
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 8 deletions.
16 changes: 12 additions & 4 deletions ext/lmdb-safe/lmdb-safe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ std::shared_ptr<MDBEnv> getMDBEnv(const char* fname, int flags, int mode, uint64
}


MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
MDBDbi MDBEnv::openDB(const string_view dbname, int flags, bool readonly)
{
unsigned int envflags;
mdb_env_get_flags(d_env, &envflags);
Expand All @@ -210,7 +210,14 @@ MDBDbi MDBEnv::openDB(const string_view dbname, int flags)
*/
std::lock_guard<std::mutex> l(d_openmut);

if(!(envflags & MDB_RDONLY)) {
// For a variety of reasons, such as the need to automagically convert older
// databases, the mdb environment may not have MDB_RDONLY set. But if our
// caller will only perform read-only transactions, give it a
// MDBROTransaction.
// If we don't do this, recursive lookups may end up triggering the
// "a writer can not exist with anyone else" exception in
// MDBRWTransactionImpl::openRWTransaction() below.
if(!(envflags & MDB_RDONLY) && !readonly) {

Check warning on line 220 in ext/lmdb-safe/lmdb-safe.cc

View workflow job for this annotation

GitHub Actions / Analyze (cpp, auth)

implicit conversion 'unsigned int' -> bool (readability-implicit-bool-conversion - Level=Warning)

Check warning on line 220 in ext/lmdb-safe/lmdb-safe.cc

View workflow job for this annotation

GitHub Actions / Analyze (cpp, dnsdist)

implicit conversion 'unsigned int' -> bool (readability-implicit-bool-conversion - Level=Warning)
auto rwt = getRWTransaction();
MDBDbi ret = rwt->openDB(dbname, flags);
rwt->commit();
Expand All @@ -235,8 +242,9 @@ MDBRWTransactionImpl::MDBRWTransactionImpl(MDBEnv *parent, MDB_txn *txn):
MDB_txn *MDBRWTransactionImpl::openRWTransaction(MDBEnv *env, MDB_txn *parent, int flags)
{
MDB_txn *result;
if(env->getROTX() || env->getRWTX())
throw std::runtime_error("Duplicate RW transaction");
if(env->getROTX() != 0 || env->getRWTX() != 0) {
throw std::runtime_error("RW transaction while other transactions are active");
}

if(int rc=mdb_txn_begin(env->d_env, parent, flags, &result))
throw std::runtime_error("Unable to start RW transaction: "+std::string(mdb_strerror(rc)));
Expand Down
2 changes: 1 addition & 1 deletion ext/lmdb-safe/lmdb-safe.hh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public:
// but, elsewhere, docs say database handles do not need to be closed?
}

MDBDbi openDB(string_view dbname, int flags);
MDBDbi openDB(string_view dbname, int flags, bool readonly = false);

MDBRWTransaction getRWTransaction();
MDBROTransaction getROTransaction();
Expand Down
6 changes: 3 additions & 3 deletions modules/lmdbbackend/lmdbbackend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");

auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE, false);

opened = true;

Expand Down Expand Up @@ -1255,7 +1255,7 @@ std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTran
if (!shard.env) {
shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
MDB_NOSUBDIR | d_asyncFlag, 0600);
shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
shard.dbi = shard.env->openDB("records_v5", MDB_CREATE, false);
}
auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
ret->db = std::make_shared<RecordsDB>(shard);
Expand All @@ -1272,7 +1272,7 @@ std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTran
}
shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
MDB_NOSUBDIR | d_asyncFlag, 0600);
shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
shard.dbi = shard.env->openDB("records_v5", MDB_CREATE, true);
}

if (rwtxn) {
Expand Down

0 comments on commit e900845

Please sign in to comment.