]>
git.ipfire.org Git - thirdparty/pdns.git/blob - ext/lmdb-safe/lmdb-safe.cc
1 #include "lmdb-safe.hh"
10 using std :: runtime_error
;
14 static string
MDBError ( int rc
)
16 return mdb_strerror ( rc
);
19 MDBDbi :: MDBDbi ( MDB_env
* env
, MDB_txn
* txn
, const string_view dbname
, int flags
)
21 // A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
23 int rc
= mdb_dbi_open ( txn
, dbname
. empty () ? 0 : & dbname
[ 0 ], flags
, & d_dbi
);
25 throw std :: runtime_error ( "Unable to open named database: " + MDBError ( rc
));
27 // Database names are keys in the unnamed database, and may be read but not written.
30 MDBEnv :: MDBEnv ( const char * fname
, int flags
, int mode
, uint64_t mapsizeMB
)
32 mdb_env_create (& d_env
);
33 if ( mdb_env_set_mapsize ( d_env
, mapsizeMB
* 1048576 ))
34 throw std :: runtime_error ( "setting map size" );
36 Various other options may also need to be set before opening the handle, e.g. mdb_env_set_mapsize(), mdb_env_set_maxreaders(), mdb_env_set_maxdbs(),
39 mdb_env_set_maxdbs ( d_env
, 128 );
41 // we need MDB_NOTLS since we rely on its semantics
42 if ( int rc
= mdb_env_open ( d_env
, fname
, flags
| MDB_NOTLS
, mode
)) {
43 // If this function fails, mdb_env_close() must be called to discard the MDB_env handle.
45 throw std :: runtime_error ( "Unable to open database file " + std :: string ( fname
)+ ": " + MDBError ( rc
));
48 if (( flags
& MDB_RDONLY
) == 0 ) {
49 // Check for stale readers to prevent unbridled database growth.
50 // Only do this when in RW mode since it affects the file.
51 mdb_reader_check ( d_env
, nullptr );
55 void MDBEnv :: incROTX ()
57 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
58 ++ d_ROtransactionsOut
[ std :: this_thread :: get_id ()];
61 void MDBEnv :: decROTX ()
63 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
64 -- d_ROtransactionsOut
[ std :: this_thread :: get_id ()];
67 void MDBEnv :: incRWTX ()
69 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
70 ++ d_RWtransactionsOut
[ std :: this_thread :: get_id ()];
73 void MDBEnv :: decRWTX ()
75 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
76 -- d_RWtransactionsOut
[ std :: this_thread :: get_id ()];
81 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
82 return d_RWtransactionsOut
[ std :: this_thread :: get_id ()];
86 std :: lock_guard
< std :: mutex
> l ( d_countmutex
);
87 return d_ROtransactionsOut
[ std :: this_thread :: get_id ()];
91 std :: shared_ptr
< MDBEnv
> getMDBEnv ( const char * fname
, int flags
, int mode
, uint64_t mapsizeMB
)
99 static std :: map
< tuple
< dev_t
, ino_t
>, Value
> s_envs
;
100 static std :: mutex mut
;
103 if ( stat ( fname
, & statbuf
)) {
105 throw std :: runtime_error ( "Unable to stat prospective mdb database: " + string ( strerror ( errno
)));
107 std :: lock_guard
< std :: mutex
> l ( mut
);
108 auto fresh
= std :: make_shared
< MDBEnv
>( fname
, flags
, mode
, mapsizeMB
);
109 if ( stat ( fname
, & statbuf
))
110 throw std :: runtime_error ( "Unable to stat prospective mdb database: " + string ( strerror ( errno
)));
111 auto key
= std :: tie ( statbuf
. st_dev
, statbuf
. st_ino
);
112 s_envs
[ key
] = { fresh
, flags
};
117 std :: lock_guard
< std :: mutex
> l ( mut
);
118 auto key
= std :: tie ( statbuf
. st_dev
, statbuf
. st_ino
);
119 auto iter
= s_envs
. find ( key
);
120 if ( iter
!= s_envs
. end ()) {
121 auto sp
= iter
-> second
. wp
. lock ();
123 if ( iter
-> second
. flags
!= flags
)
124 throw std :: runtime_error ( "Can't open mdb with differing flags" );
129 s_envs
. erase ( iter
); // useful if make_shared fails
133 auto fresh
= std :: make_shared
< MDBEnv
>( fname
, flags
, mode
, mapsizeMB
);
134 s_envs
[ key
] = { fresh
, flags
};
140 MDBDbi
MDBEnv :: openDB ( const string_view dbname
, int flags
)
142 unsigned int envflags
;
143 mdb_env_get_flags ( d_env
, & envflags
);
145 This function must not be called from multiple concurrent transactions in the same process. A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
147 std :: lock_guard
< std :: mutex
> l ( d_openmut
);
149 if (!( envflags
& MDB_RDONLY
)) {
150 auto rwt
= getRWTransaction ();
151 MDBDbi ret
= rwt
-> openDB ( dbname
, flags
);
158 auto rwt
= getROTransaction ();
159 ret
= rwt
-> openDB ( dbname
, flags
);
164 MDBRWTransactionImpl :: MDBRWTransactionImpl ( MDBEnv
* parent
, MDB_txn
* txn
):
165 MDBROTransactionImpl ( parent
, txn
)
171 MDB_txn
* MDBRWTransactionImpl :: openRWTransaction ( MDBEnv
* env
, MDB_txn
* parent
, int flags
)
174 if ( env
-> getROTX () || env
-> getRWTX ())
175 throw std :: runtime_error ( "Duplicate RW transaction" );
177 if ( int rc
= mdb_txn_begin ( env
-> d_env
, parent
, flags
, & result
))
178 throw std :: runtime_error ( "Unable to start RW transaction: " + std :: string ( mdb_strerror ( rc
)));
184 MDBRWTransactionImpl :: MDBRWTransactionImpl ( MDBEnv
* parent
, int flags
):
185 MDBRWTransactionImpl ( parent
, openRWTransaction ( parent
, nullptr , flags
))
189 MDBRWTransactionImpl ::~ MDBRWTransactionImpl ()
194 void MDBRWTransactionImpl :: commit ()
201 if ( int rc
= mdb_txn_commit ( d_txn
)) {
202 throw std :: runtime_error ( "committing: " + std :: string ( mdb_strerror ( rc
)));
204 environment (). decRWTX ();
208 void MDBRWTransactionImpl :: abort ()
215 mdb_txn_abort ( d_txn
);
216 // prevent the RO destructor from cleaning up the transaction itself
217 environment (). decRWTX ();
221 MDBROTransactionImpl :: MDBROTransactionImpl ( MDBEnv
* parent
, MDB_txn
* txn
):
229 MDB_txn
* MDBROTransactionImpl :: openROTransaction ( MDBEnv
* env
, MDB_txn
* parent
, int flags
)
232 throw std :: runtime_error ( "Duplicate RO transaction" );
235 A transaction and its cursors must only be used by a single thread, and a thread may only have a single transaction at a time. If MDB_NOTLS is in use, this does not apply to read-only transactions. */
236 MDB_txn
* result
= nullptr ;
238 if ( int rc
= mdb_txn_begin ( env
-> d_env
, parent
, MDB_RDONLY
| flags
, & result
))
239 throw std :: runtime_error ( "Unable to start RO transaction: " + string ( mdb_strerror ( rc
)));
246 void MDBROTransactionImpl :: closeROCursors ()
248 // we need to move the vector away to ensure that the cursors don’t mess with our iteration.
249 std :: vector
< MDBROCursor
*> buf
;
250 std :: swap ( d_cursors
, buf
);
251 for ( auto & cursor
: buf
) {
256 MDBROTransactionImpl :: MDBROTransactionImpl ( MDBEnv
* parent
, int flags
):
257 MDBROTransactionImpl ( parent
, openROTransaction ( parent
, nullptr , flags
))
262 MDBROTransactionImpl ::~ MDBROTransactionImpl ()
264 // this is safe because C++ will not call overrides of virtual methods in destructors.
268 void MDBROTransactionImpl :: abort ()
271 // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
274 mdb_txn_abort ( d_txn
); // this appears to work better than abort for r/o database opening
279 void MDBROTransactionImpl :: commit ()
282 // if d_txn is non-nullptr here, either the transaction object was invalidated earlier (e.g. by moving from it), or it is an RW transaction which has already cleaned up the d_txn pointer (with an abort).
285 mdb_txn_commit ( d_txn
); // this appears to work better than abort for r/o database opening
292 void MDBRWTransactionImpl :: clear ( MDB_dbi dbi
)
294 if ( int rc
= mdb_drop ( d_txn
, dbi
, 0 )) {
295 throw runtime_error ( "Error clearing database: " + MDBError ( rc
));
299 MDBRWCursor
MDBRWTransactionImpl :: getRWCursor ( const MDBDbi
& dbi
)
302 int rc
= mdb_cursor_open ( d_txn
, dbi
, & cursor
);
304 throw std :: runtime_error ( "Error creating RO cursor: " + std :: string ( mdb_strerror ( rc
)));
306 return MDBRWCursor ( d_rw_cursors
, cursor
);
309 MDBRWCursor
MDBRWTransactionImpl :: getCursor ( const MDBDbi
& dbi
)
311 return getRWCursor ( dbi
);
314 MDBRWTransaction
MDBRWTransactionImpl :: getRWTransaction ()
317 if ( int rc
= mdb_txn_begin ( environment (), * this , 0 , & txn
)) {
318 throw std :: runtime_error ( std :: string ( "failed to start child transaction: " )+ mdb_strerror ( rc
));
320 // we need to increase the counter here because commit/abort on the child transaction will decrease it
321 environment (). incRWTX ();
322 return MDBRWTransaction ( new MDBRWTransactionImpl (& environment (), txn
));
325 MDBROTransaction
MDBRWTransactionImpl :: getROTransaction ()
327 return getRWTransaction ();
330 MDBROTransaction
MDBEnv :: getROTransaction ()
332 return MDBROTransaction ( new MDBROTransactionImpl ( this ));
334 MDBRWTransaction
MDBEnv :: getRWTransaction ()
336 return MDBRWTransaction ( new MDBRWTransactionImpl ( this ));
340 void MDBRWTransactionImpl :: closeRWCursors ()
342 decltype ( d_rw_cursors
) buf
;
343 std :: swap ( d_rw_cursors
, buf
);
344 for ( auto & cursor
: buf
) {
349 MDBROCursor
MDBROTransactionImpl :: getCursor ( const MDBDbi
& dbi
)
351 return getROCursor ( dbi
);
354 MDBROCursor
MDBROTransactionImpl :: getROCursor ( const MDBDbi
& dbi
)
357 int rc
= mdb_cursor_open ( d_txn
, dbi
, & cursor
);
359 throw std :: runtime_error ( "Error creating RO cursor: " + std :: string ( mdb_strerror ( rc
)));
361 return MDBROCursor ( d_cursors
, cursor
);