Refactor concurrency handling and file operations for improved performance and thread safety

This commit is contained in:
ImBenji
2025-12-04 20:15:44 +00:00
parent fa50810212
commit 55c69aebc2
10 changed files with 214 additions and 189 deletions

View File

@@ -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;

View File

@@ -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;
};