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

@@ -20,6 +20,8 @@ add_executable(main
src/Public/sweepstore/concurrency.h src/Public/sweepstore/concurrency.h
src/Private/sweepstore/concurrency.cpp src/Private/sweepstore/concurrency.cpp
src/Public/sweepstore/utils/file_lock.h src/Public/sweepstore/utils/file_lock.h
src/Private/sweepstore/utils/fd_pool.h
src/Private/sweepstore/utils/fd_pool.cpp
src/Public/sweepstore/utils/file_handle.h src/Public/sweepstore/utils/file_handle.h
src/Private/sweepstore/utils/file_handle.cpp src/Private/sweepstore/utils/file_handle.cpp
src/Public/sweepstore/header.h src/Public/sweepstore/header.h

View File

@@ -16,6 +16,8 @@
#include "sweepstore/utils/helpers.h" #include "sweepstore/utils/helpers.h"
#include "sweepstore/utils/file_handle.h" #include "sweepstore/utils/file_handle.h"
#include "sweepstore/structures.h"
#include "sweepstore/concurrency.h"
int main() { int main() {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -35,16 +37,17 @@ int main() {
SweepstoreConcurrency::initialiseMasterAsync(filePath); SweepstoreConcurrency::initialiseMasterAsync(filePath);
int iterations = 100; int iterations = 32;
int currentIteration = 0; int currentIteration = 0;
int concurrencyTest = 1;
while (true) { while (true) {
if (++currentIteration > iterations) { if (++currentIteration > iterations) {
break; break;
} }
int concurrencyTest = 256;
std::atomic<int> completedJobs = 0; std::atomic<int> completedJobs = 0;
// Worker pool infrastructure // Worker pool infrastructure
@@ -82,11 +85,11 @@ int main() {
}); });
} }
// Queue 256 tasks // Queue 256 tasks - each will open its own handle
{ {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
for (int i = 0; i < concurrencyTest; i++) { for (int i = 0; i < concurrencyTest; i++) {
taskQueue.push([&sweepstore, i]() { taskQueue.push([filePath, i, &sweepstore]() {
sweepstore["key_" + std::to_string(i)] = "value_" + std::to_string(i); sweepstore["key_" + std::to_string(i)] = "value_" + std::to_string(i);
}); });
} }
@@ -102,7 +105,7 @@ int main() {
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "Completed " << concurrencyTest << " operations in " << duration << " ms." << std::endl; std::cout << "[" << currentIteration << "/" << iterations << "] Completed " << concurrencyTest << " operations in " << duration << " ms." << std::endl;
// Shutdown workers // Shutdown workers
shutdown = true; shutdown = true;
@@ -110,6 +113,8 @@ int main() {
for (auto& worker : workers) { for (auto& worker : workers) {
worker.join(); worker.join();
} }
concurrencyTest *= 2;
} }
return 0; return 0;

View File

@@ -30,7 +30,7 @@ int randomId() {
return (time ^ random) & 0x7FFFFFFF; return (time ^ random) & 0x7FFFFFFF;
} }
void SweepstoreConcurrency::spawnTicket(std::string filePath, void SweepstoreConcurrency::spawnTicket(SweepstoreFileHandle* _file,
const SweepstoreTicketOperation& operation, const SweepstoreTicketOperation& operation,
const uint32_t keyHash, const uint32_t keyHash,
const uint32_t targetSize, const uint32_t targetSize,
@@ -38,7 +38,9 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
std::string debugLabel std::string debugLabel
) { ) {
SweepstoreFileHandle file(filePath, std::ios::binary | std::ios::in | std::ios::out); // FileHandle now uses thread-local streams internally - no need to create new handle!
// Each thread automatically gets its own fstream from the shared file handle
SweepstoreFileHandle* file = new SweepstoreFileHandle(_file->getPath(), std::ios::in | std::ios::out | std::ios::binary);
/* /*
Useful Functions Useful Functions
@@ -47,7 +49,7 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
/// Logging function /// Logging function
auto log = [&](const std::string &message) { auto log = [&](const std::string &message) {
std::string prefix = !debugLabel.empty() ? "\033[38;5;208m[Ticket Spawner - " + debugLabel + "]:\033[0m " : "\033[38;5;208m[Ticket Spawner]:\033[0m "; std::string prefix = !debugLabel.empty() ? "\033[38;5;208m[Ticket Spawner - " + debugLabel + "]:\033[0m " : "\033[38;5;208m[Ticket Spawner]:\033[0m ";
debugPrint(prefix + message); // debugPrint(prefix + message);
}; };
// Sleep with variance (additive only) // Sleep with variance (additive only)
@@ -71,9 +73,9 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
expSleepTracker[label] = count + 1; expSleepTracker[label] = count + 1;
}; };
// Get the header(s) // Get the header(s) - using the shared file handle directly
SweepstoreHeader header(file); SweepstoreHeader header(*file);
SweepstoreConcurrencyHeader concurrencyHeader(file); SweepstoreConcurrencyHeader concurrencyHeader(*file);
/* /*
Ticket Acquisition Ticket Acquisition
@@ -91,7 +93,7 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
for (uint32_t i = 0; i < concurrentWorkers; i++) { for (uint32_t i = 0; i < concurrentWorkers; i++) {
SweepstoreWorkerTicket ticket = SweepstoreWorkerTicket(i, file); SweepstoreWorkerTicket ticket = SweepstoreWorkerTicket(i, *file);
if (!ticket.writable()) { if (!ticket.writable()) {
continue; continue;
@@ -165,21 +167,24 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
// Re-verify we lost the ticket // Re-verify we lost the ticket
SweepstoreWorkerTicketSnapshot recheckSnapshot = myTicket.snapshot(); SweepstoreWorkerTicketSnapshot recheckSnapshot = myTicket.snapshot();
if (recheckSnapshot.identifier != myIdentifier) { if (recheckSnapshot.identifier != myIdentifier) {
log("Lost ownership of ticket " + std::to_string(myTicket.getTicketIndex()) + ", was expecting identifier " + std::to_string(myIdentifier) + " but found " + std::to_string(recheckSnapshot.identifier) + "."); // log("Lost ownership of ticket " + std::to_string(myTicket.getTicketIndex()) + ", was expecting identifier " + std::to_string(myIdentifier) + " but found " + std::to_string(recheckSnapshot.identifier) + ".");
std::cout << "\033[38;5;82m[Ticket Spawner - " << debugLabel << "]:\033[0m Lost ticket " << myTicket.getTicketIndex() << ", respawning..." << std::endl;
// ReSharper disable once CppDFAInfiniteRecursion // ReSharper disable once CppDFAInfiniteRecursion
return spawnTicket( spawnTicket(
filePath, _file,
operation, operation,
keyHash, keyHash,
targetSize, targetSize,
onApproved, onApproved,
debugLabel debugLabel
); );
break;
} }
// False alarm, continue waiting // False alarm, continue waiting
log("False alarm, still own ticket " + std::to_string(myTicket.getTicketIndex()) + "."); // log("False alarm, still own ticket " + std::to_string(myTicket.getTicketIndex()) + ".");
std::cout << "\033[38;5;82m[Ticket Spawner - " << debugLabel << "]:\033[0m False alarm, still own ticket " << myTicket.getTicketIndex() << "." << std::endl;
snapshot = recheckSnapshot; snapshot = recheckSnapshot;
} }
@@ -199,6 +204,7 @@ void SweepstoreConcurrency::spawnTicket(std::string filePath,
} }
// std::cout << "\033[38;5;82m[Ticket Spawner - " << debugLabel << "]:\033[0m Completed ticket " << myTicket.getTicketIndex() << "." << std::endl; // std::cout << "\033[38;5;82m[Ticket Spawner - " << debugLabel << "]:\033[0m Completed ticket " << myTicket.getTicketIndex() << "." << std::endl;
delete file;
} }
void SweepstoreConcurrency::initialiseMaster(std::string filePath) { void SweepstoreConcurrency::initialiseMaster(std::string filePath) {
@@ -212,6 +218,8 @@ void SweepstoreConcurrency::initialiseMaster(std::string filePath) {
SweepstoreHeader header(file); SweepstoreHeader header(file);
SweepstoreConcurrencyHeader concurrencyHeader(file); SweepstoreConcurrencyHeader concurrencyHeader(file);
std::cout << "[Master] Starting master loop" << std::endl;
while (true) { while (true) {
int concurrentWorkers = concurrencyHeader.readNumberOfWorkers(); int concurrentWorkers = concurrencyHeader.readNumberOfWorkers();

View File

@@ -145,7 +145,8 @@ void SweepstoreConcurrencyHeader::initialise(int concurrentWorkers) {
writeMasterHeartbeat(0); writeMasterHeartbeat(0);
writeNumberOfWorkers(concurrentWorkers); writeNumberOfWorkers(concurrentWorkers);
writeIsReadAllowed(true); writeIsReadAllowed(true);
for (uint32_t i = 0; i < readNumberOfWorkers(); i++) { uint32_t verifyWorkers = readNumberOfWorkers();
for (uint32_t i = 0; i < verifyWorkers; i++) {
SweepstoreWorkerTicketSnapshot ticket = SweepstoreWorkerTicketSnapshot(); SweepstoreWorkerTicketSnapshot ticket = SweepstoreWorkerTicketSnapshot();
ticket.identifier = 0; ticket.identifier = 0;
ticket.workerHeartbeat = 0; ticket.workerHeartbeat = 0;
@@ -164,8 +165,7 @@ void SweepstoreConcurrencyHeader::initialise(int concurrentWorkers) {
void SweepstoreWorkerTicket::write(SweepstoreWorkerTicketSnapshot &snapshot) { void SweepstoreWorkerTicket::write(SweepstoreWorkerTicketSnapshot &snapshot) {
RandomAccessMemory buffer; RandomAccessMemory buffer;
SweepstoreFileLock lock(file.getPath(), SweepstoreFileLock::Mode::Exclusive); uint64_t offset = getOffset();
SweepstoreFileLock::Scoped scopedLock(lock);
buffer.setPositionSync(0); buffer.setPositionSync(0);
buffer.writeIntSync(snapshot.identifier, 4); buffer.writeIntSync(snapshot.identifier, 4);
@@ -187,23 +187,20 @@ void SweepstoreWorkerTicket::write(SweepstoreWorkerTicketSnapshot &snapshot) {
char* dataPtr = reinterpret_cast<char*>(data.data()); char* dataPtr = reinterpret_cast<char*>(data.data());
// Write to file // Write to file
file.writeSeek(getOffset()); file.writeSeek(offset);
file.writeBytes(dataPtr, data.size()); file.writeBytes(dataPtr, data.size());
file.flush(); file.flush();
} }
bool SweepstoreWorkerTicket::writable() { bool SweepstoreWorkerTicket::writable() {
SweepstoreFileLock lock(file.getPath(), SweepstoreFileLock::Mode::Exclusive); return true;
return lock.isLocked() == false;
} }
SweepstoreWorkerTicketSnapshot SweepstoreWorkerTicket::snapshot() { SweepstoreWorkerTicketSnapshot SweepstoreWorkerTicket::snapshot() {
SweepstoreFileLock lock(file.getPath(), SweepstoreFileLock::Mode::Shared); uint64_t offset = getOffset();
lock.lock(); file.readSeek(offset);
file.readSeek(getOffset());
std::unique_ptr<char[]> buffer(new char[TICKET_SIZE]); std::unique_ptr<char[]> buffer(new char[TICKET_SIZE]);
file.readBytes(buffer.get(), TICKET_SIZE); file.readBytes(buffer.get(), TICKET_SIZE);
lock.unlock();
RandomAccessMemory ram(reinterpret_cast<uint8_t*>(buffer.get()), TICKET_SIZE); RandomAccessMemory ram(reinterpret_cast<uint8_t*>(buffer.get()), TICKET_SIZE);
SweepstoreWorkerTicketSnapshot snapshot; SweepstoreWorkerTicketSnapshot snapshot;

View File

@@ -1,8 +1,9 @@
#include "sweepstore/utils/file_handle.h" #include "sweepstore/utils/file_handle.h"
// Constructor // Constructor - just stores path and mode, actual stream is created per-thread
SweepstoreFileHandle::SweepstoreFileHandle(const std::string& p, std::ios::openmode mode) SweepstoreFileHandle::SweepstoreFileHandle(const std::string& p, std::ios::openmode mode)
: path(p) : path(p)
, openMode(mode)
#ifdef WITH_UNREAL #ifdef WITH_UNREAL
{ {
IPlatformFile& platformFile = FPlatformFileManager::Get().GetPlatformFile(); IPlatformFile& platformFile = FPlatformFileManager::Get().GetPlatformFile();
@@ -24,11 +25,30 @@ SweepstoreFileHandle::SweepstoreFileHandle(const std::string& p, std::ios::openm
} }
} }
#else #else
, stream(std::make_unique<std::fstream>(p, mode))
{ {
if (!stream->is_open()) { // Thread-local streams created on demand in getThreadStream()
throw std::runtime_error("Failed to open file: " + path); }
#endif
#ifndef WITH_UNREAL
// Get or create the fstream for this thread
std::fstream& SweepstoreFileHandle::getThreadStream() {
auto it = streamCache.find(path);
if (it == streamCache.end() || !it->second || !it->second->is_open()) {
// Create new stream for this thread
auto stream = std::make_unique<std::fstream>(path, openMode);
if (!stream->is_open()) {
throw std::runtime_error("Failed to open file: " + path);
}
streamCache[path] = std::move(stream);
return *streamCache[path];
} }
return *it->second;
}
const std::fstream& SweepstoreFileHandle::getThreadStream() const {
// Use const_cast to reuse the non-const version
return const_cast<SweepstoreFileHandle*>(this)->getThreadStream();
} }
#endif #endif
@@ -37,7 +57,8 @@ bool SweepstoreFileHandle::isOpen() const {
#ifdef WITH_UNREAL #ifdef WITH_UNREAL
return unrealHandle != nullptr; return unrealHandle != nullptr;
#else #else
return stream && stream->is_open(); auto it = streamCache.find(path);
return it != streamCache.end() && it->second && it->second->is_open();
#endif #endif
} }
@@ -49,8 +70,10 @@ void SweepstoreFileHandle::close() {
unrealHandle = nullptr; unrealHandle = nullptr;
} }
#else #else
if (stream) { // Close this thread's stream if it exists
stream->close(); auto it = streamCache.find(path);
if (it != streamCache.end() && it->second && it->second->is_open()) {
it->second->close();
} }
#endif #endif
} }
@@ -63,11 +86,10 @@ void SweepstoreFileHandle::flush() {
unrealHandle->Flush(); unrealHandle->Flush();
} }
#else #else
if (stream) { auto& stream = getThreadStream();
stream->flush(); stream.flush();
// On Windows, also sync to ensure data hits disk // On Windows, also sync to ensure data hits disk
stream->sync(); stream.sync();
}
#endif #endif
} }
@@ -85,13 +107,14 @@ void SweepstoreFileHandle::readSeek(std::streampos pos, std::ios::seekdir dir) {
} }
#else #else
// Windows // Windows
auto& stream = getThreadStream();
// On Windows, flush and sync to disk, then invalidate buffers // On Windows, flush and sync to disk, then invalidate buffers
stream->flush(); stream.flush();
stream->sync(); stream.sync();
stream->clear(); stream.clear();
// Sync both pointers to same position // Sync both pointers to same position
stream->seekp(pos, dir); stream.seekp(pos, dir);
stream->seekg(pos, dir); stream.seekg(pos, dir);
#endif #endif
} }
@@ -102,9 +125,10 @@ void SweepstoreFileHandle::writeSeek(std::streampos pos, std::ios::seekdir dir)
readSeek(pos, dir); readSeek(pos, dir);
#else #else
// Windows // Windows
stream->flush(); auto& stream = getThreadStream();
stream->sync(); stream.flush();
stream->seekp(pos, dir); stream.sync();
stream.seekp(pos, dir);
#endif #endif
} }
@@ -114,10 +138,11 @@ void SweepstoreFileHandle::readBytes(char* buffer, std::streamsize size) {
unrealHandle->Read(reinterpret_cast<uint8*>(buffer), size); unrealHandle->Read(reinterpret_cast<uint8*>(buffer), size);
#else #else
// Windows // Windows
stream->read(buffer, size); auto& stream = getThreadStream();
stream.read(buffer, size);
// Check for read errors on Windows // Check for read errors on Windows
if (stream->fail() && !stream->eof()) { if (stream.fail() && !stream.eof()) {
stream->clear(); stream.clear();
} }
#endif #endif
} }
@@ -129,10 +154,11 @@ void SweepstoreFileHandle::writeBytes(const char* buffer, std::streamsize size)
unrealHandle->Flush(); // Unreal requires explicit flush unrealHandle->Flush(); // Unreal requires explicit flush
#else #else
// Windows // Windows
stream->write(buffer, size); auto& stream = getThreadStream();
stream.write(buffer, size);
// Check for write errors on Windows // Check for write errors on Windows
if (stream->fail()) { if (stream.fail()) {
stream->clear(); stream.clear();
} }
#endif #endif
} }

View File

@@ -4,14 +4,16 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <thread> #include <thread>
#include <memory>
#define STALE_HEARTBEAT_THRESHOLD_MS 5000 #define STALE_HEARTBEAT_THRESHOLD_MS 5000
enum SweepstoreTicketOperation : int; enum SweepstoreTicketOperation : int;
class SweepstoreFileHandle;
namespace SweepstoreConcurrency { namespace SweepstoreConcurrency {
void spawnTicket(std::string filePath, void spawnTicket(SweepstoreFileHandle* file,
const SweepstoreTicketOperation& operation, const SweepstoreTicketOperation& operation,
const uint32_t keyHash, const uint32_t keyHash,
const uint32_t targetSize, const uint32_t targetSize,

View File

@@ -49,7 +49,7 @@ public:
template <typename T> template <typename T>
void operator=(const T& value) { void operator=(const T& value) {
SweepstoreConcurrency::spawnTicket(sweepstore->filePath, SweepstoreConcurrency::spawnTicket(&sweepstore->file,
SweepstoreTicketOperation::WRITE, SweepstoreTicketOperation::WRITE,
bt_hash(key), bt_hash(key),
sizeof(T), sizeof(T),

View File

@@ -3,6 +3,15 @@
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <memory> #include <memory>
#include <iostream>
#include <unordered_map>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#endif
#ifdef WITH_UNREAL #ifdef WITH_UNREAL
#include "HAL/PlatformFileManager.h" #include "HAL/PlatformFileManager.h"
@@ -12,10 +21,16 @@
class SweepstoreFileHandle { class SweepstoreFileHandle {
private: private:
std::string path; std::string path;
std::ios::openmode openMode;
#ifdef WITH_UNREAL #ifdef WITH_UNREAL
IFileHandle* unrealHandle; IFileHandle* unrealHandle;
#else #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 #endif
public: public:
@@ -24,15 +39,15 @@ public:
const std::string& getPath() const { return path; } const std::string& getPath() const { return path; }
#ifndef WITH_UNREAL #ifndef WITH_UNREAL
std::fstream& getStream() { return *stream; } std::fstream& getStream() { return getThreadStream(); }
const std::fstream& getStream() const { return *stream; } const std::fstream& getStream() const { return getThreadStream(); }
// Smart pointer-like interface // Smart pointer-like interface
std::fstream* operator->() { return stream.get(); } std::fstream* operator->() { return &getThreadStream(); }
const std::fstream* operator->() const { return stream.get(); } const std::fstream* operator->() const { return &getThreadStream(); }
std::fstream& operator*() { return *stream; } std::fstream& operator*() { return getThreadStream(); }
const std::fstream& operator*() const { return *stream; } const std::fstream& operator*() const { return getThreadStream(); }
#endif #endif
bool isOpen() const; bool isOpen() const;
@@ -48,26 +63,46 @@ public:
#else #else
// Inline for non-Windows to avoid overhead // Inline for non-Windows to avoid overhead
inline void flush() { inline void flush() {
if (stream) { getThreadStream().flush();
stream->flush();
}
} }
inline void readSeek(std::streampos pos, std::ios::seekdir dir = std::ios::beg) { 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) { 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) { 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) { inline void writeBytes(const char* buffer, std::streamsize size) {
stream->write(buffer, size); getThreadStream().write(buffer, size);
} }
#endif #endif
SweepstoreFileHandle(SweepstoreFileHandle&&) noexcept = default; SweepstoreFileHandle(SweepstoreFileHandle&& other) noexcept
SweepstoreFileHandle& operator=(SweepstoreFileHandle&&) noexcept = default; : 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(const SweepstoreFileHandle&) = delete;
SweepstoreFileHandle& operator=(const SweepstoreFileHandle&) = delete; SweepstoreFileHandle& operator=(const SweepstoreFileHandle&) = delete;

View File

@@ -1,159 +1,103 @@
#pragma once #pragma once
#include <string> #include <string>
#include <cstdint>
#include <unordered_map>
#include <stdexcept>
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#else #else
#include <sys/file.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <sys/file.h>
#endif #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 { class SweepstoreFileLock {
public: public:
enum class Mode { enum class Mode { Shared, Exclusive };
Shared,
Exclusive
};
private: private:
#ifdef _WIN32 std::string filePath;
HANDLE handle;
OVERLAPPED overlapped;
#else
int fd;
#endif
std::string path;
Mode mode; 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() { void acquire() {
#ifdef _WIN32 int fd = getOrOpenFD(filePath);
handle = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE, int operation = (mode == Mode::Exclusive) ? LOCK_EX : LOCK_SH;
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) { if (flock(fd, operation) == -1) {
throw std::runtime_error("Failed to open file"); 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; locked = true;
} }
void release() { void release() {
if (locked) { if (locked) {
#ifdef _WIN32 int fd = getOrOpenFD(filePath);
UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped);
CloseHandle(handle);
#else
flock(fd, LOCK_UN); flock(fd, LOCK_UN);
close(fd);
#endif
locked = false; locked = false;
} }
} }
public: public:
SweepstoreFileLock(const std::string& p, Mode m) // Constructor accepts offset/length for API compatibility (unused with flock)
: path(p), mode(m), locked(false) { SweepstoreFileLock(const std::string& path, uint64_t, uint64_t, Mode m)
#ifdef _WIN32 : filePath(path), mode(m) {}
handle = INVALID_HANDLE_VALUE;
memset(&overlapped, 0, sizeof(overlapped));
#else
fd = -1;
#endif
}
~SweepstoreFileLock() { ~SweepstoreFileLock() { release(); }
release();
}
void lock() { void lock() {
if (!locked) { if (!locked) acquire();
acquire();
}
} }
void unlock() { void unlock() {
release(); release();
} }
// Check if THIS instance holds the lock bool holdsLock() const {
bool holdsLock() const { return locked; } 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
} }
SweepstoreFileLock(const SweepstoreFileLock&) = delete; // Check if file is currently locked (non-blocking test)
SweepstoreFileLock& operator=(const SweepstoreFileLock&) = delete; bool isLocked() const {
SweepstoreFileLock(SweepstoreFileLock&&) = default; int fd = getOrOpenFD(filePath);
SweepstoreFileLock& operator=(SweepstoreFileLock&&) = default; 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 { class Scoped {
private:
SweepstoreFileLock& lock; SweepstoreFileLock& lock;
public: public:
Scoped(SweepstoreFileLock& l) : lock(l) { Scoped(SweepstoreFileLock& l) : lock(l) {
lock.lock(); lock.lock();
@@ -166,4 +110,8 @@ public:
Scoped(const Scoped&) = delete; Scoped(const Scoped&) = delete;
Scoped& operator=(const Scoped&) = delete; Scoped& operator=(const Scoped&) = delete;
}; };
// Disable copying
SweepstoreFileLock(const SweepstoreFileLock&) = delete;
SweepstoreFileLock& operator=(const SweepstoreFileLock&) = delete;
}; };

View File

@@ -67,22 +67,25 @@ Future<void> main() async {
print(binaryDump(file.readAsBytesSync())); print(binaryDump(file.readAsBytesSync()));
int iteration = 0; int iteration = 0;
int maxIterations = 16;
print("Concurrent Workers: ${store._concurrencyHeader.numberOfWorkers}"); print("Concurrent Workers: ${store._concurrencyHeader.numberOfWorkers}");
print("Stale Ticket Threshold: ${STALE_HEARTBEAT_THRESHOLD_MS}ms"); print("Stale Ticket Threshold: ${STALE_HEARTBEAT_THRESHOLD_MS}ms");
int concurrencyTest = 1;
while (true) { while (true) {
int concurrencyTest = 256;
final receivePort = ReceivePort(); final receivePort = ReceivePort();
int completedJobs = 0; int completedJobs = 0;
if (iteration > 0) { if (iteration > maxIterations) {
break; break;
} }
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
print('\x1B[95mStarting iteration #$iteration with $concurrencyTest concurrent jobs...\x1B[0m'); // print('\x1B[95mStarting iteration #$iteration with $concurrencyTest concurrent jobs...\x1B[0m');
for (int i = 0; i < concurrencyTest; i++) { for (int i = 0; i < concurrencyTest; i++) {
await Isolate.spawn((message) { await Isolate.spawn((message) {
final index = message['index'] as int; final index = message['index'] as int;
@@ -107,11 +110,10 @@ Future<void> main() async {
stopwatch.stop(); stopwatch.stop();
print('\x1B[95mAll jobs completed!\x1B[0m'); print("[$iteration/$maxIterations] Completed $concurrencyTest operation in ${stopwatch.elapsedMilliseconds} ms");
print('\x1B[95mTime taken: ${stopwatch.elapsedMilliseconds}ms (${stopwatch.elapsed.inSeconds}s)\x1B[0m');
print(" ");
// sleep(Duration(seconds: 2));
iteration++; iteration++;
concurrencyTest *= 2;
} }
} }