188 lines
5.5 KiB
C++
188 lines
5.5 KiB
C++
#pragma once
|
|
#include <atomic>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <set>
|
|
#include <shared_mutex>
|
|
#include <unordered_map>
|
|
|
|
#include "utils/file.hpp"
|
|
#include "utils/rw_lock.hpp"
|
|
#include "utils/spin_lock.hpp"
|
|
#include "utils/synchronized.hpp"
|
|
|
|
namespace utils {
|
|
|
|
/**
|
|
* Helper class used for safer modifying and reading of files
|
|
* by preventing a deletion of a file until the file is not used in any of
|
|
* currently running threads.
|
|
* Also, while a single thread modyifies it's list of locked files, the deletion
|
|
* of ALL the files is delayed.
|
|
*
|
|
* Basic usage of FileRetainer consists of following parts:
|
|
* - Defining a global FileRetainer object which is used for locking and
|
|
* deleting of the files.
|
|
* - Each thread that wants to lock a single or multiple files first creates a
|
|
* FileLocker object.
|
|
* - Modifying a FileLocker is only possible through the FileLockerAccessor.
|
|
* - FileLockerAccessor prevents deletion of any file, so you can safely add
|
|
* multiple files to the locker with no risk of having files deleted during
|
|
* the process.
|
|
* - You can also add directories to the locker which prevents deletion
|
|
* of ANY files in that directory.
|
|
* - After a FileLocker or FileLockerAccessor is destroyed, FileRetainer scans
|
|
* the list of the files that wait to be deleted, and deletes all the files
|
|
* that are not inside any of currently present lockers.
|
|
*
|
|
* e.g.
|
|
* FileRetainer file_retainer;
|
|
* std::filesystem::path file1;
|
|
* std::filesystem::path file2;
|
|
*
|
|
* void Foo() {
|
|
* // I want to lock a list of files
|
|
* // Create a locker
|
|
* auto locker = file_retainer.AddLocker();
|
|
* {
|
|
* // Create accessor to the locker so you can
|
|
* // add the files which need to be locked.
|
|
* // Accesor prevents deletion of any files
|
|
* // so you safely add multiple files in atomic way
|
|
* auto accessor = locker.Access();
|
|
* accessor.AddPath(file1);
|
|
* accessor.AddPath(file2);
|
|
* }
|
|
* // DO SOMETHING WITH THE FILES
|
|
* }
|
|
*
|
|
* void Bar() {
|
|
* // I want to delete file1.
|
|
* file_retiner.DeleteFile(file1);
|
|
* }
|
|
*
|
|
* int main() {
|
|
* // Run Foo() and Bar() in different threads.
|
|
* }
|
|
*
|
|
*/
|
|
class FileRetainer {
|
|
public:
|
|
struct FileLockerAccessor;
|
|
|
|
/**
|
|
* A single locker inside the FileRetainer that contains a list
|
|
* of files that are guarded from deletion.
|
|
*/
|
|
struct FileLocker {
|
|
friend FileRetainer;
|
|
~FileLocker();
|
|
|
|
/**
|
|
* Access the FileLocker so you can modify it.
|
|
*/
|
|
FileLockerAccessor Access();
|
|
|
|
FileLocker(const FileLocker &) = delete;
|
|
FileLocker(FileLocker &&) = default;
|
|
FileLocker &operator=(const FileLocker &) = delete;
|
|
FileLocker &operator=(FileLocker &&) = default;
|
|
|
|
private:
|
|
explicit FileLocker(FileRetainer *retainer, size_t locker_id) : file_retainer_{retainer}, locker_id_{locker_id} {}
|
|
|
|
FileRetainer *file_retainer_;
|
|
size_t locker_id_;
|
|
};
|
|
|
|
/**
|
|
* Accessor to the FileLocker.
|
|
* All the modification to the FileLocker are done
|
|
* using this struct.
|
|
*/
|
|
struct FileLockerAccessor {
|
|
friend FileLocker;
|
|
|
|
/**
|
|
* Add a single path to the current locker.
|
|
*/
|
|
bool AddPath(const std::filesystem::path &path);
|
|
|
|
/**
|
|
* Remove a single path form the current locker.
|
|
*/
|
|
bool RemovePath(const std::filesystem::path &path);
|
|
|
|
FileLockerAccessor(const FileLockerAccessor &) = delete;
|
|
FileLockerAccessor(FileLockerAccessor &&) = default;
|
|
FileLockerAccessor &operator=(const FileLockerAccessor &) = delete;
|
|
FileLockerAccessor &operator=(FileLockerAccessor &&) = default;
|
|
|
|
~FileLockerAccessor();
|
|
|
|
private:
|
|
explicit FileLockerAccessor(FileRetainer *retainer, size_t locker_id);
|
|
|
|
FileRetainer *file_retainer_;
|
|
std::shared_lock<utils::RWLock> retainer_guard_;
|
|
size_t locker_id_;
|
|
};
|
|
|
|
/**
|
|
* Delete a file.
|
|
* If the file is inside any of the lockers or some thread is modifying
|
|
* any of the lockers, the file will be deleted after all the locks are
|
|
* lifted.
|
|
*/
|
|
void DeleteFile(const std::filesystem::path &path);
|
|
|
|
/**
|
|
* Create and return a new locker.
|
|
*/
|
|
FileLocker AddLocker();
|
|
|
|
/**
|
|
* Delete the files that were queued for deletion.
|
|
* This is already called after a locker is destroyed.
|
|
* Call this only if you want to trigger cleaning of the
|
|
* queue before a locker is destroyed (e.g. a file was removed
|
|
* from a locker).
|
|
* This method CANNOT be called from a thread which has an active
|
|
* accessor as it will produce a deadlock.
|
|
*/
|
|
void CleanQueue();
|
|
|
|
explicit FileRetainer() = default;
|
|
FileRetainer(const FileRetainer &) = delete;
|
|
FileRetainer(FileRetainer &&) = delete;
|
|
FileRetainer &operator=(const FileRetainer &) = delete;
|
|
FileRetainer &operator=(FileRetainer &&) = delete;
|
|
|
|
~FileRetainer();
|
|
|
|
private:
|
|
[[nodiscard]] bool FileLocked(const std::filesystem::path &path);
|
|
void DeleteOrAddToQueue(const std::filesystem::path &path);
|
|
|
|
utils::RWLock main_lock_{RWLock::Priority::WRITE};
|
|
|
|
std::atomic<size_t> active_accessors_{0};
|
|
std::atomic<size_t> next_locker_id_{0};
|
|
|
|
class LockerEntry {
|
|
public:
|
|
void LockPath(const std::filesystem::path &path);
|
|
bool RemovePath(const std::filesystem::path &path);
|
|
[[nodiscard]] bool LocksFile(const std::filesystem::path &path) const;
|
|
|
|
private:
|
|
std::set<std::filesystem::path> directories_;
|
|
std::set<std::filesystem::path> files_;
|
|
};
|
|
|
|
utils::Synchronized<std::unordered_map<size_t, LockerEntry>, utils::SpinLock> lockers_;
|
|
utils::Synchronized<std::set<std::filesystem::path>, utils::SpinLock> files_for_deletion_;
|
|
};
|
|
|
|
} // namespace utils
|