Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F112355
D120.1732300068.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D120.1732300068.diff
View Options
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,6 +12,7 @@
qt-job.cpp
${CMAKE_CURRENT_BINARY_DIR}/kazv-version.cpp
matrix-sdk.cpp
+ kazv-session-lock-guard.cpp
matrix-room.cpp
matrix-room-list.cpp
matrix-room-timeline.cpp
diff --git a/src/kazv-session-lock-guard.hpp b/src/kazv-session-lock-guard.hpp
new file mode 100644
--- /dev/null
+++ b/src/kazv-session-lock-guard.hpp
@@ -0,0 +1,21 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+#include <kazv-defs.hpp>
+#include <memory>
+#include <filesystem>
+
+class KazvSessionLockGuard
+{
+public:
+ KazvSessionLockGuard(const std::filesystem::path &sessionDir);
+ ~KazvSessionLockGuard();
+
+private:
+ struct Private;
+ std::unique_ptr<Private> m_d;
+};
diff --git a/src/kazv-session-lock-guard.cpp b/src/kazv-session-lock-guard.cpp
new file mode 100644
--- /dev/null
+++ b/src/kazv-session-lock-guard.cpp
@@ -0,0 +1,64 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <kazv-defs.hpp>
+#include <kazv-platform.hpp>
+#include <cstring>
+#if !KAZV_IS_WINDOWS
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+#include "kazv-session-lock-guard.hpp"
+#include "kazv-log.hpp"
+
+struct KazvSessionLockGuard::Private
+{
+ std::string lockFilePath;
+ int fd{-1};
+
+ void cleanup()
+ {
+#if !KAZV_IS_WINDOWS
+ qCDebug(kazvLog) << "KazvSessionLockGuard::Private::cleanup" << QString::fromStdString(lockFilePath);
+ if (fd != -1) {
+ qCDebug(kazvLog) << "unlocking session";
+ lockf(fd, F_ULOCK, 0);
+ close(fd);
+ fd = -1;
+ }
+#endif
+ }
+
+ Private(const std::filesystem::path &sessionDir)
+ : lockFilePath((sessionDir / "lock").string())
+ {
+#if !KAZV_IS_WINDOWS
+ qCDebug(kazvLog) << "KazvSessionLockGuard::Private::Private" << QString::fromStdString(lockFilePath);
+ fd = open(lockFilePath.data(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ throw std::runtime_error{std::string("Cannot open lock file: ") + std::strerror(errno)};
+ }
+
+ auto res = lockf(fd, F_TLOCK, 0);
+ if (res == -1) {
+ cleanup();
+ throw std::runtime_error{std::string("Cannot grab lock file: ") + std::strerror(errno)};
+ }
+#endif
+ }
+
+ ~Private()
+ {
+ cleanup();
+ }
+};
+
+KazvSessionLockGuard::KazvSessionLockGuard(const std::filesystem::path &sessionDir)
+ : m_d(std::make_unique<Private>(sessionDir))
+{
+}
+
+KazvSessionLockGuard::~KazvSessionLockGuard() = default;
diff --git a/src/matrix-sdk.hpp b/src/matrix-sdk.hpp
--- a/src/matrix-sdk.hpp
+++ b/src/matrix-sdk.hpp
@@ -43,6 +43,7 @@
Q_OBJECT
QML_ELEMENT
+ QString m_userDataDir;
std::unique_ptr<MatrixSdkPrivate> m_d;
/// @param d A dynamically allocated d-pointer, whose ownership
@@ -78,7 +79,7 @@
private:
// Replaces the store with another one
- void emplace(std::optional<Kazv::SdkModel> model);
+ void emplace(std::optional<Kazv::SdkModel> model, std::unique_ptr<KazvSessionLockGuard> lockGuard);
Q_SIGNALS:
void trigger(Kazv::KazvEvent e);
diff --git a/src/matrix-sdk.cpp b/src/matrix-sdk.cpp
--- a/src/matrix-sdk.cpp
+++ b/src/matrix-sdk.cpp
@@ -44,6 +44,7 @@
#include "matrix-user-given-attrs-map.hpp"
#include "kazv-log.hpp"
#include "matrix-utils.hpp"
+#include "kazv-session-lock-guard.hpp"
using namespace Kazv;
// Sdk with qt event loop, identity transform and no enhancers
@@ -78,10 +79,11 @@
struct MatrixSdkPrivate
{
- MatrixSdkPrivate(MatrixSdk *q, bool testing);
- MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model);
+ MatrixSdkPrivate(MatrixSdk *q, bool testing, std::unique_ptr<KazvSessionLockGuard> lockGuard);
+ MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model, std::unique_ptr<KazvSessionLockGuard> lockGuard);
bool testing;
std::string userDataDir;
+ std::unique_ptr<KazvSessionLockGuard> lockGuard;
RandomInterface randomGenerator;
QThread *thread;
QObject *obj;
@@ -126,25 +128,25 @@
explicit CleanupHelper(std::unique_ptr<MatrixSdkPrivate> d)
: oldD(std::move(d))
{
- // After the thread's event loop is finished, we can delete the root object
- connect(oldD->thread, &QThread::finished, oldD->obj, &QObject::deleteLater);
- connect(oldD->obj, &QObject::destroyed, this, &QObject::deleteLater);
}
- std::shared_ptr<MatrixSdkPrivate> oldD;
+ std::unique_ptr<MatrixSdkPrivate> oldD;
void cleanup()
{
qCInfo(kazvLog) << "start to clean up everything";
oldD->clientOnSecondaryRoot.stopSyncing()
- .then([oldD=oldD](auto &&) {
+ .then([obj=oldD->obj, thread=oldD->thread](auto &&) {
qCDebug(kazvLog) << "stopped syncing";
- QMetaObject::invokeMethod(oldD->obj, [thread=oldD->thread]() {
+ QMetaObject::invokeMethod(obj, [thread]() {
thread->quit();
});
});
oldD->thread->wait();
oldD->thread->deleteLater();
+ // After the thread's event loop is finished, we can delete the root object
+ oldD->obj->deleteLater();
+ this->deleteLater();
qCInfo(kazvLog) << "thread is done";
}
};
@@ -178,6 +180,15 @@
return;
}
+ if (!lockGuard) {
+ try {
+ lockGuard = std::make_unique<KazvSessionLockGuard>(sessionDir);
+ } catch (const std::runtime_error &e) {
+ qCWarning(kazvLog) << "Error locking session: " << e.what();
+ return;
+ }
+ }
+
{
auto storeStream = std::ofstream(storeFile);
if (! storeStream) {
@@ -199,9 +210,10 @@
}
}
-MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing)
+MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing, std::unique_ptr<KazvSessionLockGuard> lockGuard)
: testing(testing)
, userDataDir{kazvUserDataDir().toStdString()}
+ , lockGuard(std::move(lockGuard))
, randomGenerator(QtRandAdapter{})
, thread(new QThread())
, obj(new QObject())
@@ -222,9 +234,10 @@
obj->moveToThread(thread);
}
-MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model)
+MatrixSdkPrivate::MatrixSdkPrivate(MatrixSdk *q, bool testing, SdkModel model, std::unique_ptr<KazvSessionLockGuard> lockGuard)
: testing(testing)
, userDataDir{kazvUserDataDir().toStdString()}
+ , lockGuard(std::move(lockGuard))
, randomGenerator(QtRandAdapter{})
, thread(new QThread())
, obj(new QObject())
@@ -247,6 +260,7 @@
MatrixSdk::MatrixSdk(std::unique_ptr<MatrixSdkPrivate> d, QObject *parent)
: QObject(parent)
+ , m_userDataDir(kazvUserDataDir())
, m_d(std::move(d))
{
init();
@@ -300,12 +314,12 @@
}
MatrixSdk::MatrixSdk(QObject *parent)
- : MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, /* testing = */ false), parent)
+ : MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, /* testing = */ false, std::unique_ptr<KazvSessionLockGuard>()), parent)
{
}
MatrixSdk::MatrixSdk(SdkModel model, bool testing, QObject *parent)
- : MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model)), parent)
+ : MatrixSdk(std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model), std::unique_ptr<KazvSessionLockGuard>()), parent)
{
}
@@ -371,7 +385,7 @@
return new MatrixRoomList(m_d->clientOnSecondaryRoot);
}
-void MatrixSdk::emplace(std::optional<SdkModel> model)
+void MatrixSdk::emplace(std::optional<SdkModel> model, std::unique_ptr<KazvSessionLockGuard> lockGuard)
{
auto testing = m_d->testing;
auto userDataDir = m_d->userDataDir;
@@ -381,8 +395,8 @@
}
m_d = (model.has_value()
- ? std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model.value()))
- : std::make_unique<MatrixSdkPrivate>(this, testing));
+ ? std::make_unique<MatrixSdkPrivate>(this, testing, std::move(model.value()), std::move(lockGuard))
+ : std::make_unique<MatrixSdkPrivate>(this, testing, std::move(lockGuard)));
m_d->userDataDir = userDataDir;
// Re-initialize lager-qt cursors and watchable connections
@@ -436,7 +450,8 @@
bool MatrixSdk::loadSession(QString sessionName)
{
using StdPath = std::filesystem::path;
- auto loadFromSession = [this, sessionName](StdPath sessionDir) {
+ std::unique_ptr<KazvSessionLockGuard> lockGuard;
+ auto loadFromSession = [this, sessionName, &lockGuard](StdPath sessionDir) {
auto storeFile = sessionDir / "store";
auto metadataFile = sessionDir / "metadata";
@@ -488,7 +503,7 @@
qDebug() << "Error when loading session:" << QString::fromStdString(e.what());
return false;
}
- emplace(std::move(model));
+ emplace(std::move(model), std::move(lockGuard));
return true;
};
@@ -500,6 +515,12 @@
auto sessionId = parts[1].toStdString();
auto encodedUserId = encodeBase64(userId, Base64Opts::urlSafe);
auto sessionDir = userDataDir / "sessions" / encodedUserId / sessionId;
+ try {
+ lockGuard = std::make_unique<KazvSessionLockGuard>(sessionDir);
+ } catch (const std::runtime_error &e) {
+ qCWarning(kazvLog) << "Error locking session: " << e.what();
+ return false;
+ }
return loadFromSession(sessionDir);
}
qDebug(kazvLog) << "no session found for" << sessionName;
@@ -508,7 +529,7 @@
bool MatrixSdk::startNewSession()
{
- emplace(std::nullopt);
+ emplace(std::nullopt, std::unique_ptr<KazvSessionLockGuard>());
return true;
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -9,6 +9,10 @@
target_include_directories(kazvtestlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(kazvtestlib PUBLIC kazvprivlib libkazv::kazvtestfixtures)
+add_executable(matrix-sdk-session-loader matrix-sdk-session-loader.cpp matrix-sdk-sessions-test.cpp)
+target_compile_definitions(matrix-sdk-session-loader PRIVATE MATRIX_SDK_SESSIONS_TEST_NO_MAIN)
+target_link_libraries(matrix-sdk-session-loader Qt${QT_MAJOR_VERSION}::Test kazvtestlib)
+
ecm_add_tests(
qt-job-handler-test.cpp
qt-promise-handler-test.cpp
diff --git a/src/tests/matrix-sdk-session-loader.cpp b/src/tests/matrix-sdk-session-loader.cpp
new file mode 100644
--- /dev/null
+++ b/src/tests/matrix-sdk-session-loader.cpp
@@ -0,0 +1,26 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <kazv-defs.hpp>
+#include <iostream>
+#include <QString>
+#include "matrix-sdk-sessions-test.hpp"
+
+int main([[maybe_unused]] int argc, char *argv[])
+{
+ std::string userDataDir = argv[1];
+ std::string sessionName = argv[2];
+ auto sdk = MatrixSdkSessionsTest::makeMatrixSdkImpl(userDataDir);
+ auto res = sdk->loadSession(QString::fromStdString(sessionName));
+ if (!res) {
+ std::cout << "cannot load session" << std::endl;
+ return 1;
+ }
+
+ std::cout << "loaded session" << std::endl;
+ while (1) {}
+ return 0;
+}
diff --git a/src/tests/matrix-sdk-sessions-test.hpp b/src/tests/matrix-sdk-sessions-test.hpp
new file mode 100644
--- /dev/null
+++ b/src/tests/matrix-sdk-sessions-test.hpp
@@ -0,0 +1,47 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+#include <kazv-defs.hpp>
+#include <memory>
+#include <QObject>
+#include <matrix-sdk.hpp>
+
+class MatrixSdkSessionsTest : public QObject
+{
+ Q_OBJECT
+
+private:
+ std::string m_userDataDir;
+
+ void clearDir();
+ void createSession(std::string userId, std::string deviceId);
+
+public:
+ template<class ...Args>
+ static std::unique_ptr<MatrixSdk> makeMatrixSdkImpl(std::string userDataDir, Args &&...args)
+ {
+ std::unique_ptr<MatrixSdk> sdk(new MatrixSdk(std::forward<Args>(args)...));
+ sdk->setUserDataDir(userDataDir);
+ return sdk;
+ }
+
+ template<class ...Args>
+ std::unique_ptr<MatrixSdk> makeMatrixSdk(Args &&...args)
+ {
+ return makeMatrixSdkImpl(m_userDataDir, std::forward<Args>(args)...);
+ }
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+ void cleanup();
+
+ void testListSessions();
+ void testListLegacySessions();
+ void testLoadSession();
+ void testSessionLock();
+};
diff --git a/src/tests/matrix-sdk-sessions-test.cpp b/src/tests/matrix-sdk-sessions-test.cpp
--- a/src/tests/matrix-sdk-sessions-test.cpp
+++ b/src/tests/matrix-sdk-sessions-test.cpp
@@ -5,47 +5,22 @@
*/
#include <kazv-defs.hpp>
+#include <kazv-platform.hpp>
#include <memory>
#include <filesystem>
#include <fstream>
#include <QObject>
#include <QtTest>
+#include <QProcess>
+#include <QCoreApplication>
#include <crypto/base64.hpp>
-#include <matrix-sdk.hpp>
#include "test-temp.hpp"
+#include "matrix-sdk-sessions-test.hpp"
using namespace Kazv;
namespace Fs = std::filesystem;
using StdPath = Fs::path;
-class MatrixSdkSessionsTest : public QObject
-{
- Q_OBJECT
-
-private:
- std::string m_userDataDir;
-
- void clearDir();
- void createSession(std::string userId, std::string deviceId);
-
- template<class ...Args>
- std::unique_ptr<MatrixSdk> makeMatrixSdk(Args &&...args)
- {
- std::unique_ptr<MatrixSdk> sdk(new MatrixSdk(std::forward<Args>(args)...));
- sdk->setUserDataDir(m_userDataDir);
- return sdk;
- }
-
-private Q_SLOTS:
- void initTestCase();
- void init();
- void cleanup();
-
- void testListSessions();
- void testListLegacySessions();
- void testLoadSession();
-};
-
void MatrixSdkSessionsTest::initTestCase()
{
auto dir = StdPath(kazvTestTempDir()) / "sessions-test";
@@ -70,8 +45,13 @@
SdkModel model;
model.client.userId = userId;
model.client.deviceId = deviceId;
- auto sdk = makeMatrixSdk(model, /* testing = */ false);
- sdk->serializeToFile();
+ {
+ auto sdk = makeMatrixSdk(model, /* testing = */ false);
+ sdk->serializeToFile();
+ }
+ // Not ideal, but this is the only way to wait for
+ // MatrixSdkPravite to be destroyed.
+ QTest::qWait(100);
}
void MatrixSdkSessionsTest::testListSessions()
@@ -117,15 +97,44 @@
void MatrixSdkSessionsTest::testLoadSession()
{
- createSession("@mew:example.com", "device1");
+ createSession("@mew:example.com", "device4");
auto sdk = makeMatrixSdk();
- auto res = sdk->loadSession("@mew:example.com/device1");
+ auto res = sdk->loadSession("@mew:example.com/device4");
QVERIFY(res);
QCOMPARE(sdk->userId(), QStringLiteral("@mew:example.com"));
- QCOMPARE(sdk->deviceId(), QStringLiteral("device1"));
+ QCOMPARE(sdk->deviceId(), QStringLiteral("device4"));
}
-QTEST_MAIN(MatrixSdkSessionsTest)
+void MatrixSdkSessionsTest::testSessionLock()
+{
+#if KAZV_IS_WINDOWS
+ QSKIP("Skipping because session lock is not yet supported on Windows");
+#else
+ createSession("@mew:example.com", "device5");
+
+ auto program = QCoreApplication::applicationDirPath() + QStringLiteral("/matrix-sdk-session-loader");
+ QProcess proc1;
+ QStringList args{
+ QString::fromStdString(m_userDataDir),
+ QStringLiteral("@mew:example.com/device5"),
+ };
+ proc1.start(program, args);
+ proc1.waitForReadyRead();
+ auto line = proc1.readLine();
+ QCOMPARE(line, QByteArray("loaded session\n"));
+
+ QProcess proc2;
+ proc2.start(program, args);
+ auto res = proc2.waitForFinished();
+ QVERIFY(res);
+ QCOMPARE(proc2.exitStatus(), QProcess::NormalExit);
+ QCOMPARE(proc2.exitCode(), 1);
-#include "matrix-sdk-sessions-test.moc"
+ proc1.kill();
+#endif
+}
+
+#ifndef MATRIX_SDK_SESSIONS_TEST_NO_MAIN
+QTEST_MAIN(MatrixSdkSessionsTest)
+#endif
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Nov 22, 10:27 AM (2 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38865
Default Alt Text
D120.1732300068.diff (16 KB)
Attached To
Mode
D120: Use POSIX file lock to prevent two kazvs running on the same session
Attached
Detach File
Event Timeline
Log In to Comment