summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2018-01-03 15:34:35 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2018-01-03 16:11:38 +0100
commit9fa528c5fceefd53303a604625d13cf0cdbb109e (patch)
tree8f5d6c64300f54af1c6d3afcf5ba8b0cd82a4492
parent46bf82eaec0a30281d0b8deaf1ffbd06030eb997 (diff)
downloadsink-9fa528c5fceefd53303a604625d13cf0cdbb109e.tar.gz
sink-9fa528c5fceefd53303a604625d13cf0cdbb109e.zip
Use read-write locks for finer grained control to sDbi and sEnvironments
There are only a few cases where have to access the list of dbis or environments, so we can normally get away with just read-locking. This seems to fix a segfault that was possibly caused be an environment being reused that has already been freed in another thread. The read-only lock when initially retrieving the environment seems to fix that.
-rw-r--r--common/storage_lmdb.cpp37
-rw-r--r--tests/querytest.cpp72
2 files changed, 93 insertions, 16 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 600fbd1..514a4ea 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -23,28 +23,26 @@
23 23
24#include <iostream> 24#include <iostream>
25 25
26#include <QAtomicInt>
27#include <QDebug> 26#include <QDebug>
28#include <QDir> 27#include <QDir>
29#include <QReadWriteLock> 28#include <QReadWriteLock>
30#include <QString> 29#include <QString>
31#include <QTime> 30#include <QTime>
32#include <QMutex>
33#include <valgrind.h> 31#include <valgrind.h>
34
35#include <lmdb.h> 32#include <lmdb.h>
36#include "log.h" 33#include "log.h"
37 34
38namespace Sink { 35namespace Sink {
39namespace Storage { 36namespace Storage {
40 37
41 //TODO a QReadWriteLock would be a better choice because we mostly do reads. 38extern QReadWriteLock sDbisLock;
42extern QMutex sMutex; 39extern QReadWriteLock sEnvironmentsLock;
43extern QHash<QString, MDB_env *> sEnvironments; 40extern QHash<QString, MDB_env *> sEnvironments;
44extern QHash<QString, MDB_dbi> sDbis; 41extern QHash<QString, MDB_dbi> sDbis;
45 42
46 43
47QMutex sMutex; 44QReadWriteLock sDbisLock;
45QReadWriteLock sEnvironmentsLock;
48QHash<QString, MDB_env *> sEnvironments; 46QHash<QString, MDB_env *> sEnvironments;
49QHash<QString, MDB_dbi> sDbis; 47QHash<QString, MDB_dbi> sDbis;
50 48
@@ -558,6 +556,7 @@ public:
558 void startTransaction() 556 void startTransaction()
559 { 557 {
560 Q_ASSERT(!transaction); 558 Q_ASSERT(!transaction);
559 Q_ASSERT(sEnvironments.values().contains(env));
561 // auto f = [](const char *msg, void *ctx) -> int { 560 // auto f = [](const char *msg, void *ctx) -> int {
562 // qDebug() << msg; 561 // qDebug() << msg;
563 // return 0; 562 // return 0;
@@ -636,7 +635,7 @@ bool DataStore::Transaction::commit(const std::function<void(const DataStore::Er
636 //Add the created dbis to the shared environment 635 //Add the created dbis to the shared environment
637 if (!d->createdDbs.isEmpty()) { 636 if (!d->createdDbs.isEmpty()) {
638 if (!d->noLock) { 637 if (!d->noLock) {
639 sMutex.lock(); 638 sDbisLock.lockForWrite();
640 } 639 }
641 for (auto it = d->createdDbs.constBegin(); it != d->createdDbs.constEnd(); it++) { 640 for (auto it = d->createdDbs.constBegin(); it != d->createdDbs.constEnd(); it++) {
642 Q_ASSERT(!sDbis.contains(it.key())); 641 Q_ASSERT(!sDbis.contains(it.key()));
@@ -644,7 +643,7 @@ bool DataStore::Transaction::commit(const std::function<void(const DataStore::Er
644 } 643 }
645 d->createdDbs.clear(); 644 d->createdDbs.clear();
646 if (!d->noLock) { 645 if (!d->noLock) {
647 sMutex.unlock(); 646 sDbisLock.unlock();
648 } 647 }
649 } 648 }
650 649
@@ -698,18 +697,17 @@ DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray &
698 d->implicitCommit = true; 697 d->implicitCommit = true;
699 auto p = new DataStore::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction); 698 auto p = new DataStore::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction);
700 if (!d->noLock) { 699 if (!d->noLock) {
701 //This wouldn't be necessary with a read-write lock, we could read-only lock here 700 sDbisLock.lockForRead();
702 sMutex.lock();
703 } 701 }
704 if (!p->openDatabase(d->requestedRead, errorHandler)) { 702 if (!p->openDatabase(d->requestedRead, errorHandler)) {
705 if (!d->noLock) { 703 if (!d->noLock) {
706 sMutex.unlock(); 704 sDbisLock.unlock();
707 } 705 }
708 delete p; 706 delete p;
709 return DataStore::NamedDatabase(); 707 return DataStore::NamedDatabase();
710 } 708 }
711 if (!d->noLock) { 709 if (!d->noLock) {
712 sMutex.unlock(); 710 sDbisLock.unlock();
713 } 711 }
714 if (p->createdNewDbi) { 712 if (p->createdNewDbi) {
715 d->createdDbs.insert(p->createdDbName, p->dbi); 713 d->createdDbs.insert(p->createdDbName, p->dbi);
@@ -813,8 +811,11 @@ public:
813 void initEnvironment(const QString &fullPath, const DbLayout &layout) 811 void initEnvironment(const QString &fullPath, const DbLayout &layout)
814 { 812 {
815 // Ensure the environment is only created once, and that we only have one environment per process 813 // Ensure the environment is only created once, and that we only have one environment per process
814 QReadLocker locker(&sEnvironmentsLock);
816 if (!(env = sEnvironments.value(fullPath))) { 815 if (!(env = sEnvironments.value(fullPath))) {
817 QMutexLocker locker(&sMutex); 816 locker.unlock();
817 QWriteLocker envLocker(&sEnvironmentsLock);
818 QWriteLocker dbiLocker(&sDbisLock);
818 if (!(env = sEnvironments.value(fullPath))) { 819 if (!(env = sEnvironments.value(fullPath))) {
819 int rc = 0; 820 int rc = 0;
820 if ((rc = mdb_env_create(&env))) { 821 if ((rc = mdb_env_create(&env))) {
@@ -932,7 +933,10 @@ DataStore::Transaction DataStore::createTransaction(AccessMode type, const std::
932 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Failed to create transaction: Requested read/write transaction in read-only mode.")); 933 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Failed to create transaction: Requested read/write transaction in read-only mode."));
933 return Transaction(); 934 return Transaction();
934 } 935 }
935 936 QReadLocker locker(&sEnvironmentsLock);
937 if (!sEnvironments.values().contains(d->env)) {
938 return {};
939 }
936 return Transaction(new Transaction::Private(requestedRead, defaultErrorHandler(), d->name, d->env)); 940 return Transaction(new Transaction::Private(requestedRead, defaultErrorHandler(), d->name, d->env));
937} 941}
938 942
@@ -948,7 +952,8 @@ qint64 DataStore::diskUsage() const
948void DataStore::removeFromDisk() const 952void DataStore::removeFromDisk() const
949{ 953{
950 const QString fullPath(d->storageRoot + '/' + d->name); 954 const QString fullPath(d->storageRoot + '/' + d->name);
951 QMutexLocker locker(&sMutex); 955 QWriteLocker dbiLocker(&sDbisLock);
956 QWriteLocker envLocker(&sEnvironmentsLock);
952 SinkTrace() << "Removing database from disk: " << fullPath; 957 SinkTrace() << "Removing database from disk: " << fullPath;
953 sEnvironments.take(fullPath); 958 sEnvironments.take(fullPath);
954 for (const auto &key : sDbis.keys()) { 959 for (const auto &key : sDbis.keys()) {
@@ -967,7 +972,7 @@ void DataStore::removeFromDisk() const
967 972
968void DataStore::clearEnv() 973void DataStore::clearEnv()
969{ 974{
970 QMutexLocker locker(&sMutex); 975 QWriteLocker locker(&sEnvironmentsLock);
971 for (auto env : sEnvironments) { 976 for (auto env : sEnvironments) {
972 mdb_env_close(env); 977 mdb_env_close(env);
973 } 978 }
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
index 0844d72..1584c48 100644
--- a/tests/querytest.cpp
+++ b/tests/querytest.cpp
@@ -1182,6 +1182,78 @@ private slots:
1182 QCOMPARE(mail->getProperty("folders").toList().size(), 2); 1182 QCOMPARE(mail->getProperty("folders").toList().size(), 2);
1183 } 1183 }
1184 } 1184 }
1185
1186 /*
1187 * This test is here to ensure we don't crash if we call removeFromDisk with a running query.
1188 */
1189 void testRemoveFromDiskWithRunningQuery()
1190 {
1191 {
1192 // Setup
1193 Folder::Ptr folderEntity;
1194 const auto date = QDateTime(QDate(2015, 7, 7), QTime(12, 0));
1195 {
1196 Folder folder("sink.dummy.instance1");
1197 Sink::Store::create<Folder>(folder).exec().waitForFinished();
1198
1199 Sink::Query query;
1200 query.resourceFilter("sink.dummy.instance1");
1201
1202 // Ensure all local data is processed
1203 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
1204
1205 auto model = Sink::Store::loadModel<Folder>(query);
1206 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
1207 QCOMPARE(model->rowCount(), 1);
1208
1209 folderEntity = model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Folder::Ptr>();
1210 QVERIFY(!folderEntity->identifier().isEmpty());
1211
1212 {
1213 Mail mail("sink.dummy.instance1");
1214 mail.setExtractedMessageId("testSecond");
1215 mail.setFolder(folderEntity->identifier());
1216 mail.setExtractedDate(date.addDays(-1));
1217 Sink::Store::create<Mail>(mail).exec().waitForFinished();
1218 }
1219 {
1220 Mail mail("sink.dummy.instance1");
1221 mail.setExtractedMessageId("testLatest");
1222 mail.setFolder(folderEntity->identifier());
1223 mail.setExtractedDate(date);
1224 Sink::Store::create<Mail>(mail).exec().waitForFinished();
1225 }
1226 {
1227 Mail mail("sink.dummy.instance1");
1228 mail.setExtractedMessageId("testLast");
1229 mail.setFolder(folderEntity->identifier());
1230 mail.setExtractedDate(date.addDays(-2));
1231 Sink::Store::create<Mail>(mail).exec().waitForFinished();
1232 }
1233 }
1234
1235 // Test
1236 Sink::Query query;
1237 query.resourceFilter("sink.dummy.instance1");
1238 query.filter<Mail::Folder>(*folderEntity);
1239 query.sort<Mail::Date>();
1240 query.limit(1);
1241 query.setFlags(Query::LiveQuery);
1242 query.reduce<ApplicationDomain::Mail::ThreadId>(Query::Reduce::Selector::max<ApplicationDomain::Mail::Date>())
1243 .count("count")
1244 .collect<ApplicationDomain::Mail::Unread>("unreadCollected")
1245 .collect<ApplicationDomain::Mail::Important>("importantCollected");
1246
1247 // Ensure all local data is processed
1248 VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(QByteArrayList() << "sink.dummy.instance1"));
1249
1250 auto model = Sink::Store::loadModel<Mail>(query);
1251 }
1252
1253 //FIXME: this will result in a crash in the above still running query.
1254 VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1")));
1255 }
1256
1185}; 1257};
1186 1258
1187QTEST_MAIN(QueryTest) 1259QTEST_MAIN(QueryTest)