Skip to content

Commit 46f4089

Browse files
committed
Fix parallel lmdb readonly transactions
1 parent 5519f6c commit 46f4089

File tree

3 files changed

+127
-17
lines changed

3 files changed

+127
-17
lines changed

src/collection/backend/lmdb.cc

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include <string>
2323
#include <memory>
2424

25+
#include <pthread.h>
26+
2527
#include "modsecurity/variable_value.h"
2628
#include "src/utils/regex.h"
2729
#include "src/variables/variable.h"
@@ -36,21 +38,22 @@ namespace backend {
3638
#ifdef WITH_LMDB
3739

3840
LMDB::LMDB(std::string name) :
39-
Collection(name), m_env(NULL) {
40-
MDB_txn *txn;
41-
mdb_env_create(&m_env);
42-
mdb_env_open(m_env, "./modsec-shared-collections",
43-
MDB_WRITEMAP | MDB_NOSUBDIR, 0664);
44-
mdb_txn_begin(m_env, NULL, 0, &txn);
45-
mdb_dbi_open(txn, NULL, MDB_CREATE | MDB_DUPSORT, &m_dbi);
46-
mdb_txn_commit(txn);
47-
}
41+
Collection(name), m_env(NULL), isOpen(false) {}
4842

4943

5044
LMDB::~LMDB() {
5145
mdb_env_close(m_env);
5246
}
5347

48+
int LMDB::txn_begin(unsigned int flags, MDB_txn **ret) {
49+
if (!isOpen) {
50+
MDBEnvProvider* provider = MDBEnvProvider::GetInstance();
51+
m_env = provider->GetEnv();
52+
m_dbi = *(provider->GetDBI());
53+
isOpen = true;
54+
}
55+
return mdb_txn_begin(m_env, NULL, flags, ret);
56+
}
5457

5558
void LMDB::string2val(const std::string& str, MDB_val *val) {
5659
val->mv_size = sizeof(char)*(str.size());
@@ -159,7 +162,7 @@ std::unique_ptr<std::string> LMDB::resolveFirst(const std::string& var) {
159162

160163
string2val(var, &mdb_key);
161164

162-
rc = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &txn);
165+
rc = txn_begin(MDB_RDONLY, &txn);
163166
lmdb_debug(rc, "txn", "resolveFirst");
164167
if (rc != 0) {
165168
goto end_txn;
@@ -192,7 +195,7 @@ bool LMDB::storeOrUpdateFirst(const std::string &key,
192195
string2val(key, &mdb_key);
193196
string2val(value, &mdb_value);
194197

195-
rc = mdb_txn_begin(m_env, NULL, 0, &txn);
198+
rc = txn_begin(0, &txn);
196199
lmdb_debug(rc, "txn", "storeOrUpdateFirst");
197200
if (rc != 0) {
198201
goto end_txn;
@@ -240,7 +243,7 @@ void LMDB::resolveSingleMatch(const std::string& var,
240243
MDB_val mdb_value_ret;
241244
MDB_cursor *cursor;
242245

243-
rc = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &txn);
246+
rc = txn_begin(MDB_RDONLY, &txn);
244247
lmdb_debug(rc, "txn", "resolveSingleMatch");
245248
if (rc != 0) {
246249
goto end_txn;
@@ -271,7 +274,7 @@ void LMDB::store(std::string key, std::string value) {
271274
int rc;
272275
MDB_stat mst;
273276

274-
rc = mdb_txn_begin(m_env, NULL, 0, &txn);
277+
rc = txn_begin(0, &txn);
275278
lmdb_debug(rc, "txn", "store");
276279
if (rc != 0) {
277280
goto end_txn;
@@ -310,7 +313,7 @@ bool LMDB::updateFirst(const std::string &key,
310313
MDB_val mdb_value;
311314
MDB_val mdb_value_ret;
312315

313-
rc = mdb_txn_begin(m_env, NULL, 0, &txn);
316+
rc = txn_begin(0, &txn);
314317
lmdb_debug(rc, "txn", "updateFirst");
315318
if (rc != 0) {
316319
goto end_txn;
@@ -364,7 +367,7 @@ void LMDB::del(const std::string& key) {
364367
MDB_val mdb_value_ret;
365368
MDB_stat mst;
366369

367-
rc = mdb_txn_begin(m_env, NULL, 0, &txn);
370+
rc = txn_begin(0, &txn);
368371
lmdb_debug(rc, "txn", "del");
369372
if (rc != 0) {
370373
goto end_txn;
@@ -411,7 +414,7 @@ void LMDB::resolveMultiMatches(const std::string& var,
411414
size_t keySize = var.size();
412415
MDB_cursor *cursor;
413416

414-
rc = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &txn);
417+
rc = txn_begin(MDB_RDONLY, &txn);
415418
lmdb_debug(rc, "txn", "resolveMultiMatches");
416419
if (rc != 0) {
417420
goto end_txn;
@@ -465,7 +468,7 @@ void LMDB::resolveRegularExpression(const std::string& var,
465468

466469
Utils::Regex r(var, true);
467470

468-
rc = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &txn);
471+
rc = txn_begin(MDB_RDONLY, &txn);
469472
lmdb_debug(rc, "txn", "resolveRegularExpression");
470473
if (rc != 0) {
471474
goto end_txn;
@@ -503,6 +506,61 @@ void LMDB::resolveRegularExpression(const std::string& var,
503506
return;
504507
}
505508

509+
510+
MDBEnvProvider* MDBEnvProvider::provider_ = nullptr;;
511+
512+
MDBEnvProvider* MDBEnvProvider::GetInstance() {
513+
if (provider_==nullptr) {
514+
provider_ = new MDBEnvProvider();
515+
}
516+
return provider_;
517+
}
518+
519+
void MDBEnvProvider::Finalize() {
520+
if (provider_!=nullptr) {
521+
provider_->close();
522+
provider_ = nullptr;
523+
}
524+
}
525+
526+
MDBEnvProvider::MDBEnvProvider() :
527+
m_env(NULL), initialized(false) {
528+
pthread_mutex_init(&m_lock, NULL);
529+
}
530+
531+
MDB_env* MDBEnvProvider::GetEnv() {
532+
init();
533+
return m_env;
534+
}
535+
536+
MDB_dbi* MDBEnvProvider::GetDBI() {
537+
init();
538+
return &m_dbi;
539+
}
540+
541+
void MDBEnvProvider::init() {
542+
pthread_mutex_lock(&m_lock);
543+
if (!initialized) {
544+
MDB_txn *txn;
545+
mdb_env_create(&m_env);
546+
mdb_env_open(m_env, "./modsec-shared-collections",
547+
MDB_WRITEMAP | MDB_NOSUBDIR, 0664);
548+
mdb_txn_begin(m_env, NULL, 0, &txn);
549+
mdb_dbi_open(txn, NULL, MDB_CREATE | MDB_DUPSORT, &m_dbi);
550+
mdb_txn_commit(txn);
551+
}
552+
pthread_mutex_unlock(&m_lock);
553+
}
554+
555+
void MDBEnvProvider::close() {
556+
pthread_mutex_lock(&m_lock);
557+
if (initialized) {
558+
mdb_dbi_close(m_env, m_dbi);
559+
mdb_env_close(m_env);
560+
}
561+
pthread_mutex_unlock(&m_lock);
562+
}
563+
506564
#endif
507565

508566
} // namespace backend

src/collection/backend/lmdb.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,53 @@ namespace modsecurity {
4848
namespace collection {
4949
namespace backend {
5050

51+
52+
/**
53+
* The MDBEnvProvider class defines the `GetInstance` method that serves as an
54+
* alternative to constructor and lets clients access the same instance of this
55+
* class over and over. Its used to provide single MDB_env instance for each collection
56+
* that uses lmdb to store and retrieve data. That approach satisfies lmdb requirement:
57+
*
58+
* "LMDB uses POSIX locks on files, and these locks have issues if one process opens
59+
* a file multiple times. Because of this, do not mdb_env_open() a file multiple
60+
* times from a single process."
61+
*
62+
* Creation of MDB_env is delayed to moment when first transaction is opened.
63+
* This approach prevents passing env object to forked processes.
64+
* In that way next lmdb requirement be satisfied:
65+
*
66+
* "Use an MDB_env* in the process which opened it, without fork()ing."
67+
*/
68+
class MDBEnvProvider {
69+
protected:
70+
static MDBEnvProvider* provider_;
71+
MDBEnvProvider();
72+
public:
73+
MDBEnvProvider(MDBEnvProvider &other) = delete;
74+
void operator=(const MDBEnvProvider &) = delete;
75+
76+
/**
77+
* This is the static method that controls the access to the singleton
78+
* instance. On the first run, it creates a singleton object and places it
79+
* into the static field. On subsequent runs, it returns the client existing
80+
* object stored in the static field.
81+
*/
82+
static MDBEnvProvider* GetInstance();
83+
static void Finalize();
84+
85+
MDB_env* GetEnv();
86+
MDB_dbi* GetDBI();
87+
88+
private:
89+
MDB_env *m_env;
90+
MDB_dbi m_dbi;
91+
pthread_mutex_t m_lock;
92+
93+
bool initialized;
94+
void init();
95+
void close();
96+
};
97+
5198
class LMDB :
5299
public Collection {
53100
public:
@@ -75,11 +122,13 @@ class LMDB :
75122
variables::KeyExclusions &ke) override;
76123

77124
private:
125+
int txn_begin(unsigned int flags, MDB_txn **ret);
78126
void string2val(const std::string& str, MDB_val *val);
79127
void inline lmdb_debug(int rc, std::string op, std::string scope);
80128

81129
MDB_env *m_env;
82130
MDB_dbi m_dbi;
131+
bool isOpen;
83132
};
84133

85134
} // namespace backend

src/modsecurity.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ ModSecurity::~ModSecurity() {
106106
delete m_ip_collection;
107107
delete m_session_collection;
108108
delete m_user_collection;
109+
#ifdef WITH_LMDB
110+
collection::backend::MDBEnvProvider::Finalize();
111+
#endif
109112
}
110113

111114

0 commit comments

Comments
 (0)