- Add fix-itemretriever-in-case-of-concurrent-requests.patch
* Fix hangs accessing folders ("Retrieving folder contents" going on infinitely) OBS-URL: https://build.opensuse.org/package/show/KDE:Applications/akonadi-server?expand=0&rev=93
This commit is contained in:
parent
3e2629b9f9
commit
b7d299183e
@ -1,3 +1,10 @@
|
|||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Feb 24 13:14:48 UTC 2017 - lbeltrame@kde.org
|
||||||
|
|
||||||
|
- Add fix-itemretriever-in-case-of-concurrent-requests.patch
|
||||||
|
* Fix hangs accessing folders ("Retrieving folder contents"
|
||||||
|
going on infinitely)
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Thu Feb 9 06:38:57 UTC 2017 - lbeltrame@kde.org
|
Thu Feb 9 06:38:57 UTC 2017 - lbeltrame@kde.org
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ Source: %{rname}-%{version}.tar.xz
|
|||||||
Source99: akonadi-server-rpmlintrc
|
Source99: akonadi-server-rpmlintrc
|
||||||
# PATCH-FIX-UPSTREAM handle-mysql-process-crashes-gracefully.patch
|
# PATCH-FIX-UPSTREAM handle-mysql-process-crashes-gracefully.patch
|
||||||
Patch0: handle-mysql-process-crashes-gracefully.patch
|
Patch0: handle-mysql-process-crashes-gracefully.patch
|
||||||
|
# PATCH-FIX-UPSTREAM fix-itemretriever-in-case-of-concurrent-requests.patch - Fixes hangs when accessing folders
|
||||||
|
Patch1: fix-itemretriever-in-case-of-concurrent-requests.patch
|
||||||
%if 0%{?suse_version} > 1325
|
%if 0%{?suse_version} > 1325
|
||||||
BuildRequires: libboost_headers-devel
|
BuildRequires: libboost_headers-devel
|
||||||
%else
|
%else
|
||||||
@ -170,6 +172,7 @@ service.
|
|||||||
%prep
|
%prep
|
||||||
%setup -q -n %{rname}-%{version}
|
%setup -q -n %{rname}-%{version}
|
||||||
%patch0 -p1
|
%patch0 -p1
|
||||||
|
%patch1 -p1
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%cmake_kf5 -d build -- -DINSTALL_QSQLITE_IN_QT_PREFIX=TRUE -DQT_PLUGINS_DIR=%{_kf5_plugindir}
|
%cmake_kf5 -d build -- -DINSTALL_QSQLITE_IN_QT_PREFIX=TRUE -DQT_PLUGINS_DIR=%{_kf5_plugindir}
|
||||||
|
296
fix-itemretriever-in-case-of-concurrent-requests.patch
Normal file
296
fix-itemretriever-in-case-of-concurrent-requests.patch
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
From d42f94701f38c26cf3439727b50ee73cf0d09bee Mon Sep 17 00:00:00 2001
|
||||||
|
From: David Faure <faure@kde.org>
|
||||||
|
Date: Wed, 15 Feb 2017 08:43:09 +0100
|
||||||
|
Subject: Fix ItemRetriever in case of concurrent requests for the same item(s)
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
- ItemRetrievalManager must emit requestFinished() for those other requests,
|
||||||
|
otherwise the list of pending requests in ItemRetriever is never emptied
|
||||||
|
|
||||||
|
- ItemRetriever must not assume that a signal being emitted by
|
||||||
|
ItemRetrievalManager is necessarily about the request it's waiting for, it
|
||||||
|
could be for another one. So it must first check in its list of pending
|
||||||
|
requests to determine whether it should react or not.
|
||||||
|
|
||||||
|
With multithreaded unittest, checked for races with clang+tsan.
|
||||||
|
(There is one race, the connect to ItemRetrievalRequest vs the emit
|
||||||
|
in other threads, we should lock mLock before connect...)
|
||||||
|
|
||||||
|
Test Plan: new unittest
|
||||||
|
|
||||||
|
Reviewers: dvratil
|
||||||
|
|
||||||
|
Reviewed By: dvratil
|
||||||
|
|
||||||
|
Subscribers: #kde_pim
|
||||||
|
|
||||||
|
Tags: #kde_pim
|
||||||
|
|
||||||
|
Differential Revision: https://phabricator.kde.org/D4618
|
||||||
|
---
|
||||||
|
autotests/server/itemretrievertest.cpp | 174 ++++++++++++++++++++--------
|
||||||
|
src/server/storage/itemretrievalmanager.cpp | 1 +
|
||||||
|
src/server/storage/itemretriever.cpp | 21 ++--
|
||||||
|
3 files changed, 135 insertions(+), 61 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/autotests/server/itemretrievertest.cpp b/autotests/server/itemretrievertest.cpp
|
||||||
|
index fb4468d..1206b35 100644
|
||||||
|
--- a/autotests/server/itemretrievertest.cpp
|
||||||
|
+++ b/autotests/server/itemretrievertest.cpp
|
||||||
|
@@ -20,6 +20,7 @@
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTest>
|
||||||
|
#include <QTimer>
|
||||||
|
+#include <QMutex>
|
||||||
|
|
||||||
|
#include "storage/itemretriever.h"
|
||||||
|
#include "storage/itemretrievaljob.h"
|
||||||
|
@@ -144,6 +145,52 @@ private:
|
||||||
|
QMultiHash<qint64, JobResult> mJobResults;
|
||||||
|
};
|
||||||
|
|
||||||
|
+using RequestedParts = QVector<QByteArray /* FQ name */>;
|
||||||
|
+
|
||||||
|
+class ClientThread : public QThread
|
||||||
|
+{
|
||||||
|
+public:
|
||||||
|
+ ClientThread(Entity::Id itemId, const RequestedParts &requestedParts)
|
||||||
|
+ : m_itemId(itemId), m_requestedParts(requestedParts)
|
||||||
|
+ {}
|
||||||
|
+
|
||||||
|
+ void run() Q_DECL_OVERRIDE
|
||||||
|
+ {
|
||||||
|
+ // ItemRetriever should...
|
||||||
|
+ ItemRetriever retriever;
|
||||||
|
+ retriever.setItem(m_itemId);
|
||||||
|
+ retriever.setRetrieveParts(m_requestedParts);
|
||||||
|
+ QSignalSpy spy(&retriever, &ItemRetriever::itemsRetrieved);
|
||||||
|
+
|
||||||
|
+ const bool success = retriever.exec();
|
||||||
|
+
|
||||||
|
+ QMutexLocker lock(&m_mutex);
|
||||||
|
+ m_results.success = success;
|
||||||
|
+ m_results.signalsCount = spy.count();
|
||||||
|
+ if (m_results.signalsCount > 0) {
|
||||||
|
+ m_results.emittedItems = spy.at(0).at(0).value<QList<qint64>>();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ struct Results
|
||||||
|
+ {
|
||||||
|
+ bool success;
|
||||||
|
+ int signalsCount;
|
||||||
|
+ QList<qint64> emittedItems;
|
||||||
|
+ };
|
||||||
|
+ Results results() const {
|
||||||
|
+ QMutexLocker lock(&m_mutex);
|
||||||
|
+ return m_results;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+private:
|
||||||
|
+ const Entity::Id m_itemId;
|
||||||
|
+ const RequestedParts m_requestedParts;
|
||||||
|
+
|
||||||
|
+ mutable QMutex m_mutex; // protects results below
|
||||||
|
+ Results m_results;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
class ItemRetrieverTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
@@ -151,7 +198,6 @@ class ItemRetrieverTest : public QObject
|
||||||
|
|
||||||
|
using ExistingParts = QVector<QPair<QByteArray /* name */, QByteArray /* data */>>;
|
||||||
|
using AvailableParts = QVector<QPair<QByteArray /* name */, QByteArray /* data */>>;
|
||||||
|
- using RequestedParts = QVector<QByteArray /* FQ name */>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ItemRetrieverTest()
|
||||||
|
@@ -252,63 +298,89 @@ private Q_SLOTS:
|
||||||
|
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
- DbInitializer dbInitializer;
|
||||||
|
- FakeItemRetrievalJobFactory factory(dbInitializer);
|
||||||
|
- ItemRetrievalManager mgr(&factory);
|
||||||
|
- QTest::qWait(100);
|
||||||
|
-
|
||||||
|
- // Given a PimItem with existing parts
|
||||||
|
- Resource res = dbInitializer.createResource("testresource");
|
||||||
|
- Collection col = dbInitializer.createCollection("col1");
|
||||||
|
- PimItem item = dbInitializer.createItem("1", col);
|
||||||
|
- Q_FOREACH (const auto &existingPart, existingParts) {
|
||||||
|
- dbInitializer.createPart(item.id(), existingPart.first, existingPart.second);
|
||||||
|
- }
|
||||||
|
+ for (int step = 0; step < 2; ++step) {
|
||||||
|
+ DbInitializer dbInitializer;
|
||||||
|
+ FakeItemRetrievalJobFactory factory(dbInitializer);
|
||||||
|
+ ItemRetrievalManager mgr(&factory);
|
||||||
|
+ QTest::qWait(100);
|
||||||
|
+
|
||||||
|
+ // Given a PimItem with existing parts
|
||||||
|
+ Resource res = dbInitializer.createResource("testresource");
|
||||||
|
+ Collection col = dbInitializer.createCollection("col1");
|
||||||
|
+
|
||||||
|
+ // step 0: do it in the main thread, for easier debugging
|
||||||
|
+ PimItem item = dbInitializer.createItem("1", col);
|
||||||
|
+ Q_FOREACH (const auto &existingPart, existingParts) {
|
||||||
|
+ dbInitializer.createPart(item.id(), existingPart.first, existingPart.second);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- Q_FOREACH (const auto &availablePart, availableParts) {
|
||||||
|
- factory.addJobResult(item.id(), availablePart.first, availablePart.second);
|
||||||
|
- }
|
||||||
|
+ Q_FOREACH (const auto &availablePart, availableParts) {
|
||||||
|
+ factory.addJobResult(item.id(), availablePart.first, availablePart.second);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- // ItemRetriever should...
|
||||||
|
- ItemRetriever retriever;
|
||||||
|
- retriever.setItem(item.id());
|
||||||
|
- retriever.setRetrieveParts(requestedParts);
|
||||||
|
- QSignalSpy spy(&retriever, &ItemRetriever::itemsRetrieved);
|
||||||
|
+ if (step == 0) {
|
||||||
|
+ ClientThread thread(item.id(), requestedParts);
|
||||||
|
+ thread.run();
|
||||||
|
+
|
||||||
|
+ const ClientThread::Results results = thread.results();
|
||||||
|
+ // ItemRetriever should ... succeed
|
||||||
|
+ QVERIFY(results.success);
|
||||||
|
+ // Emit exactly one signal ...
|
||||||
|
+ QCOMPARE(results.signalsCount, expectedSignals);
|
||||||
|
+ // ... with that one item
|
||||||
|
+ if (expectedSignals > 0) {
|
||||||
|
+ QCOMPARE(results.emittedItems, QList<qint64>{ item.id() });
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- // Succeed
|
||||||
|
- QVERIFY(retriever.exec());
|
||||||
|
- // Run exactly one retrieval job
|
||||||
|
- QCOMPARE(factory.jobsCount(), expectedRetrievalJobs);
|
||||||
|
- // Emit exactly one signal ...
|
||||||
|
- QCOMPARE(spy.count(), expectedSignals);
|
||||||
|
- // ... with that one item
|
||||||
|
- if (expectedSignals > 0) {
|
||||||
|
- QCOMPARE(spy.at(0).at(0).value<QList<qint64>>(), QList<qint64>{ item.id() });
|
||||||
|
- }
|
||||||
|
+ // Check that the factory had exactly one retrieval job
|
||||||
|
+ QCOMPARE(factory.jobsCount(), expectedRetrievalJobs);
|
||||||
|
|
||||||
|
- // and the part exists in the DB
|
||||||
|
- const auto parts = item.parts();
|
||||||
|
- QCOMPARE(parts.count(), expectedParts);
|
||||||
|
- Q_FOREACH (const Part &dbPart, item.parts()) {
|
||||||
|
- const QString fqname = dbPart.partType().ns() + QLatin1Char(':') + dbPart.partType().name();
|
||||||
|
- if (!requestedParts.contains(fqname.toLatin1())) {
|
||||||
|
- continue;
|
||||||
|
+ } else {
|
||||||
|
+ QVector<ClientThread *> threads;
|
||||||
|
+ for (int i = 0; i < 20; ++i) {
|
||||||
|
+ threads.append(new ClientThread(item.id(), requestedParts));
|
||||||
|
+ }
|
||||||
|
+ for (int i = 0; i < threads.size(); ++i) {
|
||||||
|
+ threads.at(i)->start();
|
||||||
|
+ }
|
||||||
|
+ for (int i = 0; i < threads.size(); ++i) {
|
||||||
|
+ threads.at(i)->wait();
|
||||||
|
+ }
|
||||||
|
+ for (int i = 0; i < threads.size(); ++i) {
|
||||||
|
+ const ClientThread::Results results = threads.at(i)->results();
|
||||||
|
+ QVERIFY(results.success);
|
||||||
|
+ QCOMPARE(results.signalsCount, expectedSignals);
|
||||||
|
+ if (expectedSignals > 0) {
|
||||||
|
+ QCOMPARE(results.emittedItems, QList<qint64>{ item.id() });
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ qDeleteAll(threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto it = std::find_if(availableParts.constBegin(), availableParts.constEnd(),
|
||||||
|
- [dbPart](const QPair<QByteArray, QByteArray> &p) {
|
||||||
|
- return dbPart.partType().name().toLatin1() == p.first;
|
||||||
|
- });
|
||||||
|
- if (it == availableParts.constEnd()) {
|
||||||
|
- it = std::find_if(existingParts.constBegin(), existingParts.constEnd(),
|
||||||
|
- [fqname](const QPair<QByteArray, QByteArray> &p) {
|
||||||
|
- return fqname.toLatin1() == p.first;
|
||||||
|
- });
|
||||||
|
- QVERIFY(it != existingParts.constEnd());
|
||||||
|
- }
|
||||||
|
+ // Check that the parts now exist in the DB
|
||||||
|
+ const auto parts = item.parts();
|
||||||
|
+ QCOMPARE(parts.count(), expectedParts);
|
||||||
|
+ Q_FOREACH (const Part &dbPart, item.parts()) {
|
||||||
|
+ const QString fqname = dbPart.partType().ns() + QLatin1Char(':') + dbPart.partType().name();
|
||||||
|
+ if (!requestedParts.contains(fqname.toLatin1())) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto it = std::find_if(availableParts.constBegin(), availableParts.constEnd(),
|
||||||
|
+ [dbPart](const QPair<QByteArray, QByteArray> &p) {
|
||||||
|
+ return dbPart.partType().name().toLatin1() == p.first;
|
||||||
|
+ });
|
||||||
|
+ if (it == availableParts.constEnd()) {
|
||||||
|
+ it = std::find_if(existingParts.constBegin(), existingParts.constEnd(),
|
||||||
|
+ [fqname](const QPair<QByteArray, QByteArray> &p) {
|
||||||
|
+ return fqname.toLatin1() == p.first;
|
||||||
|
+ });
|
||||||
|
+ QVERIFY(it != existingParts.constEnd());
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- QCOMPARE(dbPart.data(), it->second);
|
||||||
|
- QCOMPARE(dbPart.datasize(), it->second.size());
|
||||||
|
+ QCOMPARE(dbPart.data(), it->second);
|
||||||
|
+ QCOMPARE(dbPart.datasize(), it->second.size());
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
diff --git a/src/server/storage/itemretrievalmanager.cpp b/src/server/storage/itemretrievalmanager.cpp
|
||||||
|
index 65f5e78..382bb9e 100644
|
||||||
|
--- a/src/server/storage/itemretrievalmanager.cpp
|
||||||
|
+++ b/src/server/storage/itemretrievalmanager.cpp
|
||||||
|
@@ -232,6 +232,7 @@ void ItemRetrievalManager::retrievalJobFinished(ItemRetrievalRequest *request, c
|
||||||
|
qCDebug(AKONADISERVER_LOG) << "someone else requested item" << request->ids << "as well, marking as processed";
|
||||||
|
(*it)->errorMsg = errorMsg;
|
||||||
|
(*it)->processed = true;
|
||||||
|
+ Q_EMIT requestFinished(*it);
|
||||||
|
it = mPendingRequests[request->resourceId].erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
diff --git a/src/server/storage/itemretriever.cpp b/src/server/storage/itemretriever.cpp
|
||||||
|
index 0ae3e91..c57c860 100644
|
||||||
|
--- a/src/server/storage/itemretriever.cpp
|
||||||
|
+++ b/src/server/storage/itemretriever.cpp
|
||||||
|
@@ -317,17 +317,18 @@ bool ItemRetriever::exec()
|
||||||
|
QEventLoop eventLoop;
|
||||||
|
connect(ItemRetrievalManager::instance(), &ItemRetrievalManager::requestFinished,
|
||||||
|
this, [&](ItemRetrievalRequest *finishedRequest) {
|
||||||
|
- if (!finishedRequest->errorMsg.isEmpty()) {
|
||||||
|
- mLastError = finishedRequest->errorMsg.toUtf8();
|
||||||
|
- eventLoop.exit(1);
|
||||||
|
- } else {
|
||||||
|
- requests.removeOne(finishedRequest);
|
||||||
|
- Q_EMIT itemsRetrieved(finishedRequest->ids);
|
||||||
|
- if (requests.isEmpty()) {
|
||||||
|
- eventLoop.quit();
|
||||||
|
- }
|
||||||
|
+ if (requests.removeOne(finishedRequest)) {
|
||||||
|
+ if (!finishedRequest->errorMsg.isEmpty()) {
|
||||||
|
+ mLastError = finishedRequest->errorMsg.toUtf8();
|
||||||
|
+ eventLoop.exit(1);
|
||||||
|
+ } else {
|
||||||
|
+ Q_EMIT itemsRetrieved(finishedRequest->ids);
|
||||||
|
+ if (requests.isEmpty()) {
|
||||||
|
+ eventLoop.quit();
|
||||||
|
}
|
||||||
|
- }, Qt::UniqueConnection);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }, Qt::UniqueConnection);
|
||||||
|
|
||||||
|
auto it = requests.begin();
|
||||||
|
while (it != requests.end()) {
|
||||||
|
--
|
||||||
|
cgit v0.11.2
|
||||||
|
|
Loading…
Reference in New Issue
Block a user