From f04edd555faef3e3aad26de86212c3f712f71a1f Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 15 Feb 2024 08:42:44 +0300 Subject: [PATCH] Open "lock" files for the same folder only once PR #20414. Closes #12203. --- src/base/asyncfilestorage.cpp | 53 ++++++++++++++++++++++++++--------- src/base/asyncfilestorage.h | 15 +++++++--- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/base/asyncfilestorage.cpp b/src/base/asyncfilestorage.cpp index 6424b1862..ba96aa09f 100644 --- a/src/base/asyncfilestorage.cpp +++ b/src/base/asyncfilestorage.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2024 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -34,31 +34,56 @@ #include "base/utils/fs.h" #include "base/utils/io.h" +QHash> AsyncFileStorage::m_reservedPaths; +QReadWriteLock AsyncFileStorage::m_reservedPathsLock; + AsyncFileStorage::AsyncFileStorage(const Path &storageFolderPath, QObject *parent) : QObject(parent) , m_storageDir(storageFolderPath) - , m_lockFile((m_storageDir / Path(u"storage.lock"_s)).data()) { Q_ASSERT(m_storageDir.isAbsolute()); - if (!Utils::Fs::mkpath(m_storageDir)) - throw AsyncFileStorageError(tr("Could not create directory '%1'.").arg(m_storageDir.toString())); + const Path lockFilePath = m_storageDir / Path(u"storage.lock"_s); - // TODO: This folder locking approach does not work for UNIX systems. Implement it. - if (!m_lockFile.open(QFile::WriteOnly)) - throw AsyncFileStorageError(m_lockFile.errorString()); + { + const QReadLocker readLocker {&m_reservedPathsLock}; + m_lockFile = m_reservedPaths.value(lockFilePath).lock(); + } + + if (!m_lockFile) + { + const QWriteLocker writeLocker {&m_reservedPathsLock}; + if (std::weak_ptr &lockFile = m_reservedPaths[lockFilePath]; lockFile.expired()) [[likely]] + { + if (!Utils::Fs::mkpath(m_storageDir)) + throw AsyncFileStorageError(tr("Could not create directory '%1'.").arg(m_storageDir.toString())); + + auto lockFileDeleter = [](QFile *file) + { + file->close(); + file->remove(); + delete file; + }; + m_lockFile = std::shared_ptr(new QFile(lockFilePath.data()), std::move(lockFileDeleter)); + + // TODO: This folder locking approach does not work for UNIX systems. Implement it. + if (!m_lockFile->open(QFile::WriteOnly)) + throw AsyncFileStorageError(m_lockFile->errorString()); + + lockFile = m_lockFile; + } + else + { + m_lockFile = lockFile.lock(); + } + } } -AsyncFileStorage::~AsyncFileStorage() -{ - m_lockFile.close(); - m_lockFile.remove(); -} +AsyncFileStorage::~AsyncFileStorage() = default; void AsyncFileStorage::store(const Path &filePath, const QByteArray &data) { - QMetaObject::invokeMethod(this, [this, data, filePath]() { store_impl(filePath, data); } - , Qt::QueuedConnection); + QMetaObject::invokeMethod(this, [this, data, filePath] { store_impl(filePath, data); }, Qt::QueuedConnection); } Path AsyncFileStorage::storageDir() const diff --git a/src/base/asyncfilestorage.h b/src/base/asyncfilestorage.h index 2943ee212..47937de1e 100644 --- a/src/base/asyncfilestorage.h +++ b/src/base/asyncfilestorage.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2024 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,19 +28,23 @@ #pragma once +#include + #include +#include #include +#include #include "base/exceptions.h" #include "base/path.h" -class AsyncFileStorageError : public RuntimeError +class AsyncFileStorageError final : public RuntimeError { public: using RuntimeError::RuntimeError; }; -class AsyncFileStorage : public QObject +class AsyncFileStorage final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(AsyncFileStorage) @@ -60,5 +64,8 @@ private: Q_INVOKABLE void store_impl(const Path &fileName, const QByteArray &data); Path m_storageDir; - QFile m_lockFile; + std::shared_ptr m_lockFile; + + static QHash> m_reservedPaths; + static QReadWriteLock m_reservedPathsLock; };