diff --git a/kde/pim/akonadi/0001-Windows-Handle-QLocalSocket-behavior-gracefully.patch b/kde/pim/akonadi/0001-Windows-Handle-QLocalSocket-behavior-gracefully.patch new file mode 100644 index 00000000..61d47574 --- /dev/null +++ b/kde/pim/akonadi/0001-Windows-Handle-QLocalSocket-behavior-gracefully.patch @@ -0,0 +1,93 @@ +From eb1725fd20b9177b4b3d499a33a8ea32415819f6 Mon Sep 17 00:00:00 2001 +From: Kevin Funk +Date: Wed, 29 Apr 2020 13:35:54 +0200 +Subject: [PATCH] Windows: Handle QLocalSocket behavior gracefully + +Summary: +On Windows the underlying implementation of QLocalSocket behaves +differently, it seems to trigger the QLocalSocket::readyRead() signal +earlier than anticipated (as on Linux). +Prior to this patch this was not handled gracefully. + +Kudos to David Faure for actually figuring this out by just looking at +the code and my debug output on Windows. :) + +BEFORE: +Let's have a look at what happens on Windows inside Akonadi's Conneciton +class: + +- Akonadi data sends Hello cmd => 91 bytes +- Akonadi resource receives data +- QLocalSocket::readyReady() signal fires +- Triggers Connection::handleIncomingData via signal-slot connection +- On Windows, QLocalSocket::bytesAvailable() is just 8 bytes +- Only the first 8 bytes are read, into the `qint64 tag` variable +- Protocol::deserialize(...) is called + - Internally calls waitForData(...) (=> waitForReadyRead()) a few times +- QLocalSocket::readyRead() signal fires again +- Connection::handleIncomingData re-entered => BUG + +Problematic end result: +``` +[10972] org.kde.pim.akonadicore: tag: -1099511627647 +[10972] org.kde.pim.akonadicore: Invalid command, the world is going to end! +[10972] org.kde.pim.akonadicore: State changed: QLocalSocket::ClosingState +[10972] org.kde.pim.akonadicore: State changed: QLocalSocket::UnconnectedState +``` + +=> Resource attempts reconnection to server, just to fail again +afterwards. + +AFTER: +The fix is to temporarily disconnect from the readyRead() signal while +attempting to wait for data to deserialize commands. This in order to never +re-enter Connection::handleIncomingData() while doing so. + +Reviewers: dfaure, mlaurent + +Subscribers: kde-pim + +Tags: #kde_pim + +Differential Revision: https://phabricator.kde.org/D29266 +--- + src/core/connection.cpp | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/src/core/connection.cpp b/src/core/connection.cpp +index 8aa0aa6f5..3e44c29f9 100644 +--- a/src/core/connection.cpp ++++ b/src/core/connection.cpp +@@ -180,6 +180,7 @@ void Connection::doReconnect() + Q_EMIT socketDisconnected(); + }); + connect(mSocket.data(), &QLocalSocket::disconnected, this, &Connection::socketDisconnected); ++ // note: we temporarily disconnect from readyRead-signal inside handleIncomingData() + connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData); + + // actually do connect +@@ -253,6 +254,10 @@ void Connection::handleIncomingData() + qint64 tag; + stream >> tag; + ++ // temporarily disconnect from readyRead-signal to avoid re-entering this function when we ++ // call waitForData() deep inside Protocol::deserialize ++ disconnect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData); ++ + Protocol::CommandPtr cmd; + try { + cmd = Protocol::deserialize(mSocket.data()); +@@ -260,6 +265,10 @@ void Connection::handleIncomingData() + qCWarning(AKONADICORE_LOG) << "Protocol exception:" << e.what(); + // cmd's type will be Invalid by default, so fall-through + } ++ ++ // reconnect to the signal again ++ connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData); ++ + if (!cmd || (cmd->type() == Protocol::Command::Invalid)) { + qCWarning(AKONADICORE_LOG) << "Invalid command, the world is going to end!"; + mSocket->close(); +-- +2.26.0.windows.1 + diff --git a/kde/pim/akonadi/akonadi.py b/kde/pim/akonadi/akonadi.py index ec8ac56b..c52a7062 100644 --- a/kde/pim/akonadi/akonadi.py +++ b/kde/pim/akonadi/akonadi.py @@ -1,59 +1,61 @@ # -*- coding: utf-8 -*- import info class subinfo(info.infoclass): def setTargets(self): self.versionInfo.setDefaultValues() self.patchToApply["17.04.0"] = [("akonadi-17.04.0-20170530.diff", 1)] self.patchToApply["17.04.1"] = [("akonadi-17.04.0-20170530.diff", 1)] self.patchToApply["17.04.2"] = [("akonadi-17.04.0-20170530.diff", 1)] self.patchToApply["17.08.3"] = [("akonadi-17.08.3-20171204.diff", 1), #workaround for locked log crash https://phabricator.kde.org/T7538#119826 ("akonadi-17.08.3-20171204-1.diff", 1), ("akonadi-17.08.3-20171204-2.diff", 1)] self.patchToApply["17.12.0"] = [("akonadi-17.12.0-20171220.diff", 1), ("0001-Win-Create-local-socket-named-pipes-based-on-the-ins.patch", 1)] self.patchToApply["18.12.2"] = [("akonadi-18.12.2-macos.diff", 1), ("akonadi-18.12.2-errorstring.diff", 1)] self.patchLevel["18.12.2"] = 2 self.patchToApply["18.12.3"] = [("akonadi-18.12.2-macos.diff", 1), ("akonadi-18.12.2-errorstring.diff", 1)] self.patchLevel["18.12.3"] = 2 self.patchToApply["19.04.1"] = [("akonadi-19.04-macos.diff", 1), ("akonadi-18.12.2-errorstring.diff", 1), ("akonadi-19.04.1-shorter-sockets.diff", 1)] # macos temp path are so long that if a user has a long nick it bypass the limited socket file path length of 104chars. self.patchLevel["19.04.1"] = 3 self.patchToApply["19.04.2"] = [("akonadi-19.04-macos.diff", 1), ("akonadi-18.12.2-errorstring.diff", 1), ("akonadi-19.04.1-shorter-sockets.diff", 1)] # macos temp path are so long that if a user has a long nick it bypass the limited socket file path length of 104chars. + self.patchToApply["20.04.0"] = [("0001-Windows-Handle-QLocalSocket-behavior-gracefully.patch", 1)] + self.description = "A storage service for PIM data and meta data" def registerOptions(self): self.options.dynamic.registerOption("useDesignerPlugin", True) def setDependencies(self): self.buildDependencies["kde/frameworks/extra-cmake-modules"] = None self.buildDependencies["libs/boost/boost-graph"] = None self.runtimeDependencies["libs/libxslt"] = None self.runtimeDependencies["libs/sqlite"] = None self.runtimeDependencies["libs/shared-mime-info"] = None self.runtimeDependencies["libs/qt5/qtbase"] = None self.runtimeDependencies["kde/frameworks/tier2/kcompletion"] = None self.runtimeDependencies["kde/frameworks/tier1/kitemmodels"] = None self.runtimeDependencies["kde/frameworks/tier3/kio"] = None if self.options.dynamic.useDesignerPlugin: self.runtimeDependencies["kde/frameworks/tier3/kdesignerplugin"] = None from Package.CMakePackageBase import * class Package(CMakePackageBase): def __init__(self): CMakePackageBase.__init__(self) self.subinfo.options.configure.args = "-DDATABASE_BACKEND=SQLITE -DAKONADI_RUN_MYSQL_ISOLATED_TESTS=OFF -DBUILD_TESTING=OFF " if not self.subinfo.options.dynamic.useDesignerPlugin: self.subinfo.options.configure.args += "-DBUILD_DESIGNERPLUGIN=OFF "