|
|
@@ -227,13 +227,34 @@ unsigned int LmdbDocumentStore::open_for_write(WriteTxn& wtxn,
|
|
|
// pre-date v2.4.4 acquire a sentinel on their next write without a
|
|
|
// migration pass. Idempotent.
|
|
|
write_subdb_identity(wtxn, raw_dbi, collection);
|
|
|
- {
|
|
|
- std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
|
- dbi_cache_[key] = raw_dbi;
|
|
|
- }
|
|
|
+
|
|
|
+ // v2.8.0 — deliberately NOT cached here.
|
|
|
+ //
|
|
|
+ // LMDB keeps a handle private to the opening transaction until it COMMITS,
|
|
|
+ // and CLOSES it if that transaction aborts. Caching at this point meant any
|
|
|
+ // write that threw afterwards (a failed mdb_put, a sentinel mismatch, a
|
|
|
+ // WriteTxn destructing uncommitted) left a CLOSED handle in the cache, and
|
|
|
+ // every later operation on that collection failed EINVAL for the rest of the
|
|
|
+ // process's life.
|
|
|
+ //
|
|
|
+ // That is the v2.4.3 bug in a third failure mode, and it was live in
|
|
|
+ // production on 2.7.1: `find` on smartbotic-automation:workflows failed with
|
|
|
+ // "LMDB cursor_open: Invalid argument" on every attempt while every other
|
|
|
+ // collection was fine, and only a restart cleared it.
|
|
|
+ //
|
|
|
+ // Callers cache via cacheCommittedDbi() AFTER their commit succeeds. Once
|
|
|
+ // committed, LMDB promotes the handle into the env's shared table and it
|
|
|
+ // stays valid for the life of the env - which is what makes caching safe at
|
|
|
+ // that point and only at that point.
|
|
|
return raw_dbi;
|
|
|
}
|
|
|
|
|
|
+void LmdbDocumentStore::cacheCommittedDbi(std::string_view collection,
|
|
|
+ unsigned int dbi) {
|
|
|
+ std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
|
+ dbi_cache_[std::string(collection)] = dbi;
|
|
|
+}
|
|
|
+
|
|
|
std::optional<unsigned int>
|
|
|
LmdbDocumentStore::try_open_for_read(ReadTxn& rtxn,
|
|
|
std::string_view collection) {
|
|
|
@@ -276,6 +297,7 @@ void LmdbDocumentStore::put(std::string_view collection,
|
|
|
MDB_val v = to_val(payload);
|
|
|
mdb_check(mdb_put(wtxn.raw(), dbi, &k, &v, 0), "put");
|
|
|
wtxn.commit();
|
|
|
+ cacheCommittedDbi(collection, dbi);
|
|
|
}
|
|
|
|
|
|
std::optional<smartbotic::database::Document>
|
|
|
@@ -309,10 +331,12 @@ bool LmdbDocumentStore::del(std::string_view collection, std::string_view id) {
|
|
|
int rc = mdb_del(wtxn.raw(), dbi, &k, nullptr);
|
|
|
if (rc == MDB_NOTFOUND) {
|
|
|
wtxn.commit();
|
|
|
+ cacheCommittedDbi(collection, dbi);
|
|
|
return false;
|
|
|
}
|
|
|
if (rc != MDB_SUCCESS) throw_mdb(rc, "del");
|
|
|
wtxn.commit();
|
|
|
+ cacheCommittedDbi(collection, dbi);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -535,6 +559,7 @@ void LmdbDocumentStore::put_vector(std::string_view collection,
|
|
|
const_cast<void*>(static_cast<const void*>(vec.data()))};
|
|
|
mdb_check(mdb_put(wtxn.raw(), dbi, &k, &v, 0), "put_vector");
|
|
|
wtxn.commit();
|
|
|
+ cacheCommittedDbi(subdb, dbi);
|
|
|
}
|
|
|
|
|
|
bool LmdbDocumentStore::del_vector(std::string_view collection, std::string_view id) {
|
|
|
@@ -553,6 +578,7 @@ bool LmdbDocumentStore::del_vector(std::string_view collection, std::string_view
|
|
|
if (rc == MDB_NOTFOUND) return false;
|
|
|
if (rc != MDB_SUCCESS) throw_mdb(rc, "del_vector");
|
|
|
wtxn.commit();
|
|
|
+ cacheCommittedDbi(subdb, dbi);
|
|
|
return true;
|
|
|
}
|
|
|
|