lmdb-safe/lmdb-various.cc

245 lines
5.2 KiB
C++
Raw Normal View History

2018-12-07 13:52:17 +01:00
#include "lmdb-safe.hh"
#include <arpa/inet.h>
2018-12-07 13:52:17 +01:00
#include <unistd.h>
#include <atomic>
#include <cstring>
2018-12-07 18:17:03 +01:00
#include <thread>
2018-12-08 14:08:26 +01:00
#include <vector>
using namespace std;
2022-01-18 22:08:36 +01:00
using namespace LMDBSafe;
2018-12-07 11:25:49 +01:00
2018-12-07 13:52:17 +01:00
static void closeTest()
2018-12-07 11:25:49 +01:00
{
2018-12-07 18:17:03 +01:00
auto env = getMDBEnv("./database", 0, 0600);
int c = MDB_CREATE;
MDBDbi dbi = env->openDB("ahu", c);
MDBDbi main = env->openDB(0, c);
MDBDbi hyc = env->openDB("hyc", c);
2018-12-07 11:25:49 +01:00
2018-12-07 18:17:03 +01:00
auto txn = env->getROTransaction();
for(auto& d : {&main, &dbi, &hyc}) {
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
auto rocursor = txn->getCursor(*d);
2018-12-10 14:51:02 +01:00
MDBOutVal key, data;
2018-12-07 18:17:03 +01:00
if(rocursor.get(key, data, MDB_FIRST))
continue;
int count=0;
do {
count++;
}while(!rocursor.get(key, data, MDB_NEXT));
cout<<"Have "<<count<<" entries"<<endl;
}
2018-12-07 11:25:49 +01:00
2018-12-07 13:52:17 +01:00
return;
2018-12-07 11:25:49 +01:00
}
2018-12-08 14:08:26 +01:00
void doPuts(int tid)
try
{
auto env = getMDBEnv("./database", 0, 0600);
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
for(int n=0; n < 15; ++n) {
auto txn = env->getRWTransaction();
int val = n + 1000*tid;
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
txn->put(dbi, val, val);
txn->commit();
2018-12-08 14:08:26 +01:00
cout << "Done with transaction "<<n<<" in thread " << tid<<endl;
}
cout<<"Done with thread "<<tid<<endl;
}
catch(std::exception& e)
{
cout<<"in thread "<<tid<<": "<<e.what()<<endl;
throw;
}
void doGets(int tid)
try
{
auto env = getMDBEnv("./database", 0, 0600);
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
for(int n=0; n < 15; ++n) {
auto txn = env->getROTransaction();
int val = n + 1000*tid;
2018-12-10 14:51:02 +01:00
MDBOutVal res;
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
if(txn->get(dbi, val, res)) {
2018-12-08 14:08:26 +01:00
throw std::runtime_error("no record");
}
cout << "Done with readtransaction "<<n<<" in thread " << tid<<endl;
}
cout<<"Done with read thread "<<tid<<endl;
}
catch(std::exception& e)
{
cout<<"in thread "<<tid<<": "<<e.what()<<endl;
throw;
}
void doFill()
{
auto env = getMDBEnv("./database", 0, 0600);
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
for(int n = 0; n < 20; ++n) {
auto txn = env->getRWTransaction();
for(int j=0; j < 1000000; ++j) {
2018-12-10 14:51:02 +01:00
MDBInVal mv(n*1000000+j);
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
txn->put(dbi, mv, mv, 0);
2018-12-08 14:08:26 +01:00
}
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
txn->commit();
2018-12-08 14:08:26 +01:00
}
cout<<"Done filling"<<endl;
}
void doMeasure()
{
auto env = getMDBEnv("./database", 0, 0600);
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
for(;;) {
for(int n = 0; n < 20; ++n) {
auto txn = env->getROTransaction();
unsigned int count=0;
for(int j=0; j < 1000000; ++j) {
2018-12-10 14:51:02 +01:00
MDBInVal mv(n*1000000+j);
MDBOutVal res;
Hide MDB*Transaction behind a unique_ptr front This is to prevent the issue with Object Slicing. With the previous solution (where MDB*Transaction are normal objects), consider the following code: MDBRWTransaction txn = env.getRWTransaction(); //! Invalid: We explicitly break this move because it would be //! unsafe: // MDBROTransaction ro_txn(std::move(txn)); //! Valid, RW inherits from RO now, so we can bind an RO //! reference to an RW transaction. MDBROTransaction &ro_txn = txn; //! Dangerous!! MDBROTransaction ro_txn2(std::move(ro_txn)); The last move there breaks the semantics of the RW transaction which is bound to the reference ro_txn. It looses its RW cursors, which remain partly inside the txn instance. All kinds of weird and bad things can happen here. For instance, the ro_txn2 would go out of scope before the txn, calling the destructor MDBROTransaction destructor (which defaults to commit instead of abort!) and only freeing parts of the cursors. Only then the MDBRWTransaction destructor is called, which will free the cursors which belong to the RW transaction which has already been committed. The only safe way to prevent Object Slicing in this scenario I could come up with is to disallow moves of the objects altogether and instead use unique_ptr as front for them. This also removes an additional dynamic allocation per RW transaction (for the cursor vector), since the address of that vector is now constant over the lifetime of the transaction without indirection.
2019-10-26 11:42:38 +02:00
if(!txn->get(dbi, mv, res))
2018-12-08 14:08:26 +01:00
++count;
}
cout<<count<<" ";
cout.flush();
if(!count)
break;
}
cout<<endl;
}
}
2018-12-07 11:25:49 +01:00
int main(int argc, char** argv)
{
2018-12-08 14:08:26 +01:00
std::thread t1(doMeasure);
std::thread t2(doFill);
t1.join();
t2.join();
}
/*
auto env = getMDBEnv("./database", 0, 0600);
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
vector<std::thread> threads;
for(int n=0; n < 100; ++n) {
std::thread t(doPuts, n);
threads.emplace_back(std::move(t));
}
for(auto& t: threads) {
t.join();
}
threads.clear();
for(int n=0; n < 100; ++n) {
std::thread t(doGets, n);
threads.emplace_back(std::move(t));
}
for(auto& t: threads) {
t.join();
}
return 0;
}
2018-12-07 13:52:17 +01:00
closeTest();
2018-12-07 18:17:03 +01:00
auto env = getMDBEnv("./database", 0, 0600);
2018-12-07 11:25:49 +01:00
MDB_stat stat;
2018-12-07 18:17:03 +01:00
mdb_env_stat(*env.get(), &stat);
2018-12-07 11:25:49 +01:00
cout << stat.ms_entries<< " entries in database"<<endl;
2018-12-07 13:52:17 +01:00
2018-12-07 18:17:03 +01:00
MDBDbi dbi = env->openDB("ahu", MDB_CREATE);
2018-12-07 11:25:49 +01:00
{
2018-12-07 18:17:03 +01:00
MDBROTransaction rotxn = env->getROTransaction();
2018-12-07 11:25:49 +01:00
{
auto rocursor = rotxn.getCursor(dbi);
MDB_val key{0,0}, data{0,0};
rocursor.get(key, data, MDB_FIRST);
int count=0;
do {
count++;
}while(!rocursor.get(key, data, MDB_NEXT));
cout<<"Counted "<<count<<" entries"<<endl;
}
int found{0}, notfound{0};
for(unsigned n=0; n < 20000000; ++n) {
unsigned int store = htonl(n);
MDB_val data;
int rc = rotxn.get(dbi, {sizeof(store), (char*)&store},
data);
if(!rc)
found++;
else if(rc == MDB_NOTFOUND)
notfound++;
else
throw std::runtime_error("error");
rotxn.reset();
rotxn.renew();
if(!(n % 1024000))
cout << n << " " <<found<< " " << notfound <<endl;
}
cout<<"Found "<<found<<", notfound: "<<notfound<<endl;
}
2018-12-07 18:17:03 +01:00
auto txn = env->getRWTransaction();
2018-12-07 11:25:49 +01:00
2018-12-07 13:52:17 +01:00
auto cursor = txn.getCursor(dbi);
2018-12-07 11:25:49 +01:00
time_t start=time(0);
ofstream delplot("plot");
for(unsigned n=0; n < 20000000*8; ++n) {
unsigned int store = htonl(n);
txn.del(dbi, {sizeof(store), (char*)&store});
if(!(n % (1024*1024))) {
cout << time(0)- start << '\t' << n << endl;
delplot << time(0)- start << '\t' << n << endl;
}
}
cout<<"Done deleting, committing"<<endl;
txn.commit();
cout<<"Done with commit"<<endl;
2018-12-07 18:17:03 +01:00
txn = env->getRWTransaction();
2018-12-07 11:25:49 +01:00
start=time(0);
ofstream plot("plot");
for(unsigned n=0; n < 20000000*8; ++n) {
int res = n*n;
unsigned int store = htonl(n);
txn.put(dbi, {sizeof(store), (char*)&store},
{sizeof(res), (char*)&res}, MDB_APPEND);
if(!(n % (1024*1024))) {
cout << time(0)- start << '\t' << n << endl;
plot << time(0)- start << '\t' << n << endl;
}
}
txn.commit();
}
2018-12-08 14:08:26 +01:00
*/
2022-01-18 22:08:36 +01:00