Refactor concurrency handling and file operations for improved performance and thread safety
This commit is contained in:
@@ -3,6 +3,15 @@
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_UNREAL
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
@@ -12,10 +21,16 @@
|
||||
class SweepstoreFileHandle {
|
||||
private:
|
||||
std::string path;
|
||||
std::ios::openmode openMode;
|
||||
#ifdef WITH_UNREAL
|
||||
IFileHandle* unrealHandle;
|
||||
#else
|
||||
std::unique_ptr<std::fstream> stream;
|
||||
// Thread-local cache: each thread gets its own fstream per path
|
||||
static thread_local std::unordered_map<std::string, std::unique_ptr<std::fstream>> streamCache;
|
||||
|
||||
// Get or create the fstream for this thread
|
||||
std::fstream& getThreadStream();
|
||||
const std::fstream& getThreadStream() const;
|
||||
#endif
|
||||
|
||||
public:
|
||||
@@ -24,15 +39,15 @@ public:
|
||||
const std::string& getPath() const { return path; }
|
||||
|
||||
#ifndef WITH_UNREAL
|
||||
std::fstream& getStream() { return *stream; }
|
||||
const std::fstream& getStream() const { return *stream; }
|
||||
std::fstream& getStream() { return getThreadStream(); }
|
||||
const std::fstream& getStream() const { return getThreadStream(); }
|
||||
|
||||
// Smart pointer-like interface
|
||||
std::fstream* operator->() { return stream.get(); }
|
||||
const std::fstream* operator->() const { return stream.get(); }
|
||||
std::fstream* operator->() { return &getThreadStream(); }
|
||||
const std::fstream* operator->() const { return &getThreadStream(); }
|
||||
|
||||
std::fstream& operator*() { return *stream; }
|
||||
const std::fstream& operator*() const { return *stream; }
|
||||
std::fstream& operator*() { return getThreadStream(); }
|
||||
const std::fstream& operator*() const { return getThreadStream(); }
|
||||
#endif
|
||||
|
||||
bool isOpen() const;
|
||||
@@ -48,26 +63,46 @@ public:
|
||||
#else
|
||||
// Inline for non-Windows to avoid overhead
|
||||
inline void flush() {
|
||||
if (stream) {
|
||||
stream->flush();
|
||||
}
|
||||
getThreadStream().flush();
|
||||
}
|
||||
inline void readSeek(std::streampos pos, std::ios::seekdir dir = std::ios::beg) {
|
||||
stream->seekg(pos, dir);
|
||||
getThreadStream().seekg(pos, dir);
|
||||
}
|
||||
inline void writeSeek(std::streampos pos, std::ios::seekdir dir = std::ios::beg) {
|
||||
stream->seekp(pos, dir);
|
||||
getThreadStream().seekp(pos, dir);
|
||||
}
|
||||
inline void readBytes(char* buffer, std::streamsize size) {
|
||||
stream->read(buffer, size);
|
||||
getThreadStream().read(buffer, size);
|
||||
}
|
||||
inline void writeBytes(const char* buffer, std::streamsize size) {
|
||||
stream->write(buffer, size);
|
||||
getThreadStream().write(buffer, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
SweepstoreFileHandle(SweepstoreFileHandle&&) noexcept = default;
|
||||
SweepstoreFileHandle& operator=(SweepstoreFileHandle&&) noexcept = default;
|
||||
SweepstoreFileHandle(SweepstoreFileHandle&& other) noexcept
|
||||
: path(std::move(other.path))
|
||||
, openMode(other.openMode)
|
||||
#ifdef WITH_UNREAL
|
||||
, unrealHandle(other.unrealHandle)
|
||||
#endif
|
||||
{
|
||||
#ifdef WITH_UNREAL
|
||||
other.unrealHandle = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
SweepstoreFileHandle& operator=(SweepstoreFileHandle&& other) noexcept {
|
||||
if (this != &other) {
|
||||
close();
|
||||
path = std::move(other.path);
|
||||
openMode = other.openMode;
|
||||
#ifdef WITH_UNREAL
|
||||
unrealHandle = other.unrealHandle;
|
||||
other.unrealHandle = nullptr;
|
||||
#endif
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SweepstoreFileHandle(const SweepstoreFileHandle&) = delete;
|
||||
SweepstoreFileHandle& operator=(const SweepstoreFileHandle&) = delete;
|
||||
|
||||
@@ -1,159 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/file.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/file.h>
|
||||
#endif
|
||||
|
||||
// Simple file lock using flock() with thread-local FD cache
|
||||
// Each thread has its own FD, flock() is per-FD, so threads don't conflict
|
||||
// Matches Dart's paradigm: each isolate has its own RandomAccessFile
|
||||
class SweepstoreFileLock {
|
||||
public:
|
||||
enum class Mode {
|
||||
Shared,
|
||||
Exclusive
|
||||
};
|
||||
enum class Mode { Shared, Exclusive };
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
HANDLE handle;
|
||||
OVERLAPPED overlapped;
|
||||
#else
|
||||
int fd;
|
||||
#endif
|
||||
std::string path;
|
||||
std::string filePath;
|
||||
Mode mode;
|
||||
bool locked;
|
||||
bool locked = false;
|
||||
|
||||
// Thread-local FD cache - each thread has its own FD per file
|
||||
static thread_local std::unordered_map<std::string, int> fdCache;
|
||||
|
||||
static int getOrOpenFD(const std::string& path) {
|
||||
auto it = fdCache.find(path);
|
||||
if (it != fdCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int fd = open(path.c_str(), O_RDWR);
|
||||
if (fd == -1) {
|
||||
throw std::runtime_error("Failed to open file for locking: " + path);
|
||||
}
|
||||
|
||||
fdCache[path] = fd;
|
||||
return fd;
|
||||
}
|
||||
|
||||
void acquire() {
|
||||
#ifdef _WIN32
|
||||
handle = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
int fd = getOrOpenFD(filePath);
|
||||
int operation = (mode == Mode::Exclusive) ? LOCK_EX : LOCK_SH;
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("Failed to open file");
|
||||
if (flock(fd, operation) == -1) {
|
||||
throw std::runtime_error("Failed to acquire file lock");
|
||||
}
|
||||
|
||||
memset(&overlapped, 0, sizeof(overlapped));
|
||||
DWORD flags = (mode == Mode::Exclusive) ? LOCKFILE_EXCLUSIVE_LOCK : 0;
|
||||
|
||||
if (!LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) {
|
||||
CloseHandle(handle);
|
||||
throw std::runtime_error("Failed to lock");
|
||||
}
|
||||
#else
|
||||
fd = open(path.c_str(), O_RDWR);
|
||||
if (fd == -1) throw std::runtime_error("Failed to open file");
|
||||
|
||||
int op = (mode == Mode::Exclusive) ? LOCK_EX : LOCK_SH;
|
||||
if (flock(fd, op) != 0) {
|
||||
close(fd);
|
||||
throw std::runtime_error("Failed to lock");
|
||||
}
|
||||
#endif
|
||||
locked = true;
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (locked) {
|
||||
#ifdef _WIN32
|
||||
UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped);
|
||||
CloseHandle(handle);
|
||||
#else
|
||||
int fd = getOrOpenFD(filePath);
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
#endif
|
||||
locked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
SweepstoreFileLock(const std::string& p, Mode m)
|
||||
: path(p), mode(m), locked(false) {
|
||||
#ifdef _WIN32
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
memset(&overlapped, 0, sizeof(overlapped));
|
||||
#else
|
||||
fd = -1;
|
||||
#endif
|
||||
}
|
||||
// Constructor accepts offset/length for API compatibility (unused with flock)
|
||||
SweepstoreFileLock(const std::string& path, uint64_t, uint64_t, Mode m)
|
||||
: filePath(path), mode(m) {}
|
||||
|
||||
~SweepstoreFileLock() {
|
||||
release();
|
||||
}
|
||||
~SweepstoreFileLock() { release(); }
|
||||
|
||||
void lock() {
|
||||
if (!locked) {
|
||||
acquire();
|
||||
}
|
||||
if (!locked) acquire();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
release();
|
||||
}
|
||||
|
||||
// Check if THIS instance holds the lock
|
||||
bool holdsLock() const { return locked; }
|
||||
|
||||
// Check if the file is locked by ANYONE (including this instance)
|
||||
bool isLocked() const {
|
||||
#ifdef _WIN32
|
||||
HANDLE testHandle = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
|
||||
if (testHandle == INVALID_HANDLE_VALUE) {
|
||||
return false; // Can't even open file
|
||||
}
|
||||
|
||||
OVERLAPPED testOverlapped;
|
||||
memset(&testOverlapped, 0, sizeof(testOverlapped));
|
||||
DWORD flags = (mode == Mode::Exclusive) ? LOCKFILE_EXCLUSIVE_LOCK : 0;
|
||||
flags |= LOCKFILE_FAIL_IMMEDIATELY; // Non-blocking
|
||||
|
||||
bool isLocked = !LockFileEx(testHandle, flags, 0, MAXDWORD, MAXDWORD, &testOverlapped);
|
||||
|
||||
if (!isLocked) {
|
||||
// We got the lock, release it
|
||||
UnlockFileEx(testHandle, 0, MAXDWORD, MAXDWORD, &testOverlapped);
|
||||
}
|
||||
|
||||
CloseHandle(testHandle);
|
||||
return isLocked;
|
||||
#else
|
||||
int testFd = open(path.c_str(), O_RDWR);
|
||||
if (testFd == -1) {
|
||||
return false; // Can't open file
|
||||
}
|
||||
|
||||
int op = (mode == Mode::Exclusive) ? LOCK_EX : LOCK_SH;
|
||||
op |= LOCK_NB; // Non-blocking
|
||||
|
||||
bool isLocked = (flock(testFd, op) != 0);
|
||||
|
||||
if (!isLocked) {
|
||||
// We got the lock, release it
|
||||
flock(testFd, LOCK_UN);
|
||||
}
|
||||
|
||||
close(testFd);
|
||||
return isLocked;
|
||||
#endif
|
||||
bool holdsLock() const {
|
||||
return locked;
|
||||
}
|
||||
|
||||
SweepstoreFileLock(const SweepstoreFileLock&) = delete;
|
||||
SweepstoreFileLock& operator=(const SweepstoreFileLock&) = delete;
|
||||
SweepstoreFileLock(SweepstoreFileLock&&) = default;
|
||||
SweepstoreFileLock& operator=(SweepstoreFileLock&&) = default;
|
||||
// Check if file is currently locked (non-blocking test)
|
||||
bool isLocked() const {
|
||||
int fd = getOrOpenFD(filePath);
|
||||
int operation = (mode == Mode::Exclusive) ? LOCK_EX : LOCK_SH;
|
||||
|
||||
// Try non-blocking lock
|
||||
if (flock(fd, operation | LOCK_NB) == -1) {
|
||||
return true; // Already locked
|
||||
}
|
||||
|
||||
// Got the lock, release immediately
|
||||
flock(fd, LOCK_UN);
|
||||
return false;
|
||||
}
|
||||
|
||||
// RAII helper for scoped locking
|
||||
class Scoped {
|
||||
private:
|
||||
SweepstoreFileLock& lock;
|
||||
|
||||
public:
|
||||
Scoped(SweepstoreFileLock& l) : lock(l) {
|
||||
lock.lock();
|
||||
@@ -166,4 +110,8 @@ public:
|
||||
Scoped(const Scoped&) = delete;
|
||||
Scoped& operator=(const Scoped&) = delete;
|
||||
};
|
||||
};
|
||||
|
||||
// Disable copying
|
||||
SweepstoreFileLock(const SweepstoreFileLock&) = delete;
|
||||
SweepstoreFileLock& operator=(const SweepstoreFileLock&) = delete;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user