diff options
-rw-r--r-- | common/storage_lmdb.cpp | 54 | ||||
-rw-r--r-- | tests/storagetest.cpp | 54 |
2 files changed, 63 insertions, 45 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index 13a6853..f0a5bc0 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -52,9 +52,11 @@ public: | |||
52 | bool readTransaction; | 52 | bool readTransaction; |
53 | bool firstOpen; | 53 | bool firstOpen; |
54 | static QMutex sMutex; | 54 | static QMutex sMutex; |
55 | static QHash<QString, MDB_env*> sEnvironments; | ||
55 | }; | 56 | }; |
56 | 57 | ||
57 | QMutex Storage::Private::sMutex; | 58 | QMutex Storage::Private::sMutex; |
59 | QHash<QString, MDB_env*> Storage::Private::sEnvironments; | ||
58 | 60 | ||
59 | Storage::Private::Private(const QString &s, const QString &n, AccessMode m) | 61 | Storage::Private::Private(const QString &s, const QString &n, AccessMode m) |
60 | : storageRoot(s), | 62 | : storageRoot(s), |
@@ -70,23 +72,30 @@ Storage::Private::Private(const QString &s, const QString &n, AccessMode m) | |||
70 | dir.mkpath(storageRoot); | 72 | dir.mkpath(storageRoot); |
71 | dir.mkdir(fullPath); | 73 | dir.mkdir(fullPath); |
72 | 74 | ||
73 | //This seems to resolve threading related issues, not sure why though | 75 | //Ensure the environment is only created once |
74 | QMutexLocker locker(&sMutex); | 76 | QMutexLocker locker(&sMutex); |
75 | 77 | ||
76 | //create file | 78 | int rc = 0; |
77 | if (mdb_env_create(&env)) { | 79 | /* |
78 | // TODO: handle error | 80 | * It seems we can only ever have one environment open in the process. |
79 | } else { | 81 | * Otherwise multi-threading breaks. |
80 | int rc = mdb_env_open(env, fullPath.toStdString().data(), 0, 0664); | 82 | */ |
81 | 83 | env = sEnvironments.value(fullPath); | |
82 | if (rc) { | 84 | if (!env) { |
83 | std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl; | 85 | if ((rc = mdb_env_create(&env))) { |
84 | mdb_env_close(env); | 86 | // TODO: handle error |
85 | env = 0; | 87 | std::cerr << "mdb_env_create: " << rc << " " << mdb_strerror(rc) << std::endl; |
86 | } else { | 88 | } else { |
87 | //FIXME: dynamic resize | 89 | if ((rc = mdb_env_open(env, fullPath.toStdString().data(), 0, 0664))) { |
88 | const size_t dbSize = (size_t)10485760 * (size_t)100 * (size_t)80; //10MB * 800 | 90 | std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl; |
89 | mdb_env_set_mapsize(env, dbSize); | 91 | mdb_env_close(env); |
92 | env = 0; | ||
93 | } else { | ||
94 | //FIXME: dynamic resize | ||
95 | const size_t dbSize = (size_t)10485760 * (size_t)100 * (size_t)80; //10MB * 800 | ||
96 | mdb_env_set_mapsize(env, dbSize); | ||
97 | sEnvironments.insert(fullPath, env); | ||
98 | } | ||
90 | } | 99 | } |
91 | } | 100 | } |
92 | } | 101 | } |
@@ -97,11 +106,12 @@ Storage::Private::~Private() | |||
97 | mdb_txn_abort(transaction); | 106 | mdb_txn_abort(transaction); |
98 | } | 107 | } |
99 | 108 | ||
100 | // it is still there and still unused, so we can shut it down | 109 | //Since we can have only one environment open per process, we currently leak the environments. |
101 | if (env) { | 110 | // if (env) { |
102 | mdb_dbi_close(env, dbi); | 111 | // //mdb_dbi_close should not be necessary and is potentially dangerous (see docs) |
103 | mdb_env_close(env); | 112 | // mdb_dbi_close(env, dbi); |
104 | } | 113 | // mdb_env_close(env); |
114 | // } | ||
105 | } | 115 | } |
106 | 116 | ||
107 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) | 117 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) |
@@ -292,7 +302,7 @@ void Storage::scan(const char *keyData, uint keySize, | |||
292 | 302 | ||
293 | rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); | 303 | rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); |
294 | if (rc) { | 304 | if (rc) { |
295 | Error error(d->name.toStdString(), rc, mdb_strerror(rc)); | 305 | Error error(d->name.toStdString(), rc, std::string("Error during mdb_cursor open: ") + mdb_strerror(rc)); |
296 | errorHandler(error); | 306 | errorHandler(error); |
297 | return; | 307 | return; |
298 | } | 308 | } |
@@ -314,15 +324,13 @@ void Storage::scan(const char *keyData, uint keySize, | |||
314 | } else { | 324 | } else { |
315 | if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) { | 325 | if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) { |
316 | resultHandler(key.mv_data, key.mv_size, data.mv_data, data.mv_size); | 326 | resultHandler(key.mv_data, key.mv_size, data.mv_data, data.mv_size); |
317 | } else { | ||
318 | std::cout << "couldn't find value " << std::string(keyData, keySize) << std::endl; | ||
319 | } | 327 | } |
320 | } | 328 | } |
321 | 329 | ||
322 | mdb_cursor_close(cursor); | 330 | mdb_cursor_close(cursor); |
323 | 331 | ||
324 | if (rc) { | 332 | if (rc) { |
325 | Error error(d->name.toStdString(), rc, mdb_strerror(rc)); | 333 | Error error(d->name.toStdString(), rc, std::string("Key: ") + std::string(keyData, keySize) + " : " + mdb_strerror(rc)); |
326 | errorHandler(error); | 334 | errorHandler(error); |
327 | } | 335 | } |
328 | 336 | ||
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp index a405de0..6c1d02b 100644 --- a/tests/storagetest.cpp +++ b/tests/storagetest.cpp | |||
@@ -46,8 +46,11 @@ private: | |||
46 | } | 46 | } |
47 | return keyMatch; | 47 | return keyMatch; |
48 | }, | 48 | }, |
49 | [&success](const Akonadi2::Storage::Error &) { success = false; } | 49 | [&success](const Akonadi2::Storage::Error &error) { |
50 | ); | 50 | qDebug() << QString::fromStdString(error.message); |
51 | success = false; | ||
52 | } | ||
53 | ); | ||
51 | return success && keyMatch; | 54 | return success && keyMatch; |
52 | } | 55 | } |
53 | 56 | ||
@@ -138,29 +141,36 @@ private Q_SLOTS: | |||
138 | const int count = 10000; | 141 | const int count = 10000; |
139 | 142 | ||
140 | populate(count); | 143 | populate(count); |
141 | 144 | // QTest::qWait(500); | |
142 | bool error = false; | 145 | |
143 | //Try to concurrently read | 146 | //We repeat the test a bunch of times since failing is relatively random |
144 | QList<QFuture<void> > futures; | 147 | for (int tries = 0; tries < 10; tries++) { |
145 | const int concurrencyLevel = 10; | 148 | bool error = false; |
146 | for (int num = 0; num < concurrencyLevel; num++) { | 149 | //Try to concurrently read |
147 | futures << QtConcurrent::run([this, count, &error](){ | 150 | QList<QFuture<void> > futures; |
148 | Akonadi2::Storage storage(testDataPath, dbName); | 151 | const int concurrencyLevel = 20; |
149 | for (int i = 0; i < count; i++) { | 152 | for (int num = 0; num < concurrencyLevel; num++) { |
150 | if (!verify(storage, i)) { | 153 | futures << QtConcurrent::run([this, count, &error](){ |
151 | error = true; | 154 | Akonadi2::Storage storage(testDataPath, dbName, Akonadi2::Storage::ReadOnly); |
152 | break; | 155 | Akonadi2::Storage storage2(testDataPath, dbName + "2", Akonadi2::Storage::ReadOnly); |
156 | for (int i = 0; i < count; i++) { | ||
157 | if (!verify(storage, i)) { | ||
158 | error = true; | ||
159 | break; | ||
160 | } | ||
153 | } | 161 | } |
154 | } | 162 | }); |
155 | }); | 163 | } |
156 | } | 164 | for(auto future : futures) { |
157 | for(auto future : futures) { | 165 | future.waitForFinished(); |
158 | future.waitForFinished(); | 166 | } |
167 | QVERIFY(!error); | ||
159 | } | 168 | } |
160 | QVERIFY(!error); | ||
161 | 169 | ||
162 | Akonadi2::Storage storage(testDataPath, dbName); | 170 | { |
163 | storage.removeFromDisk(); | 171 | Akonadi2::Storage storage(testDataPath, dbName); |
172 | storage.removeFromDisk(); | ||
173 | } | ||
164 | } | 174 | } |
165 | }; | 175 | }; |
166 | 176 | ||