Skip to content

Commit fecca28

Browse files
committed
add in-memory file locking to avoid opening a currently open file
1 parent 585b6fc commit fecca28

2 files changed

Lines changed: 67 additions & 15 deletions

File tree

cpp/MMapFile.h

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef MMAPFILE_H
22
#define MMAPFILE_H
33

4+
#include <cstdint>
45
#include <string>
56
#include <stdexcept>
67

@@ -10,10 +11,23 @@
1011
#include <sys/stat.h> // For fstat
1112
#include <fcntl.h> // For open()
1213
#include <unistd.h> // For ftruncate(), close()
14+
#include <unordered_set>
1315

1416
// #include <android/log.h>
1517
// #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "rtnmmrray", __VA_ARGS__))
1618

19+
20+
// in-memory set of open file (pairs of dev and ino)
21+
using DeviceAndInode = std::pair<uint64_t, uint64_t>;
22+
23+
struct DeviceAndInodeHash {
24+
std::size_t operator()(const DeviceAndInode& p) const {
25+
return std::hash<uint64_t>{}(p.first) ^ (std::hash<uint64_t>{}(p.second) << 1);
26+
}
27+
};
28+
29+
static std::unordered_set<DeviceAndInode, DeviceAndInodeHash> fileLocks;
30+
1731
// Helper functions
1832
static inline long long getFileSizeFromName(const std::string& path) {
1933
struct stat fileStat;
@@ -24,20 +38,21 @@ static inline long long getFileSizeFromName(const std::string& path) {
2438
return S_ISDIR(fileStat.st_mode) ? 0 : fileStat.st_size; // Return 0 for directories, otherwise return file size
2539
}
2640

27-
static inline long long getFileSizeFromFd(int fd) {
28-
struct stat fileStat;
29-
if (fstat(fd, &fileStat) != 0) {
30-
perror("fstat");
31-
return -1; // Return -1 to indicate an error
32-
}
33-
return fileStat.st_size;
34-
}
35-
3641
// Resize a file to a given size
3742
static inline bool fileResize(int fd, size_t newSize) {
3843
return ftruncate(fd, newSize) == 0;
3944
}
4045

46+
// Lock a file, i.e. add it to the in-memory set of device/inode pairs
47+
static inline bool fileLock(uint64_t dev, uint64_t ino) {
48+
return fileLocks.insert(std::make_pair(dev, ino)).second;
49+
}
50+
51+
// Unlock a file, i.e. remove it from the in-memory set of device/inode pairs
52+
static inline void fileUnlock(uint64_t dev, uint64_t ino) {
53+
fileLocks.erase(std::make_pair(dev, ino));
54+
}
55+
4156
// Create a directory and all parent directories if they do not exist
4257
static inline bool createParentDir(const std::string &path) {
4358
size_t pos = 0;
@@ -75,15 +90,19 @@ class MMapFile {
7590
capacity_(0),
7691
data_(nullptr),
7792
fd_(-1),
78-
readOnly_(false) {}
93+
readOnly_(false),
94+
dev_(-1),
95+
ino_(-1) {}
7996

8097
// Constructor
8198
MMapFile(const std::string& filePath, bool readOnly = false) :
8299
size_(0),
83100
capacity_(0),
84101
data_(nullptr),
85102
fd_(-1),
86-
readOnly_(false)
103+
readOnly_(false),
104+
dev_(-1),
105+
ino_(-1)
87106
{
88107
open(filePath, readOnly);
89108
}
@@ -140,6 +159,8 @@ class MMapFile {
140159
if (!createParentDir(filePath)) [[unlikely]] {
141160
throw std::runtime_error(std::string("Failed to create parent directory for file: ") + filePath);
142161
}
162+
163+
// Open the file
143164
fd_ = ::open(filePath.c_str(), readOnly ? O_RDONLY : O_RDWR | O_CREAT, 0600);
144165
if (fd_ < 0)
145166
{
@@ -148,19 +169,29 @@ class MMapFile {
148169
filePath_ = filePath;
149170
readOnly_ = readOnly;
150171

151-
size_ = getFileSizeFromFd(fd_);
152-
if (size_ == (size_t)-1) [[unlikely]]
153-
{
172+
struct stat fileStat;
173+
if (fstat(fd_, &fileStat) != 0) [[unlikely]] {
154174
close();
155175
throw std::runtime_error("Failed to get file size");
156176
}
157-
capacity_ = size_;
158177

178+
// Lock the file
179+
if (!fileLock(fileStat.st_dev, fileStat.st_ino)) [[unlikely]] {
180+
close();
181+
throw std::runtime_error("Failed to lock file, someone else is already using it: " + filePath);
182+
}
183+
dev_ = fileStat.st_dev;
184+
ino_ = fileStat.st_ino;
185+
186+
// Get the file size
187+
size_ = fileStat.st_size;
188+
capacity_ = size_;
159189
if (size_ == 0) [[unlikely]] {
160190
data_ = nullptr;
161191
return;
162192
}
163193

194+
// Map the file into memory
164195
data_ = static_cast<uint8_t *>(mmap(nullptr, capacity_, readOnly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
165196
if (data_ == MAP_FAILED) [[unlikely]] {
166197
close();
@@ -170,13 +201,15 @@ class MMapFile {
170201

171202
void close(bool dontTouchFile = false)
172203
{
204+
// Unmap the file from memory
173205
if (data_) {
174206
if (data_ != MAP_FAILED) {
175207
munmap(data_, capacity_);
176208
}
177209
data_ = nullptr;
178210
}
179211

212+
// Close the file, resize it if necessary, and delete it if empty
180213
if (fd_ >= 0) {
181214
if (!readOnly_ && !dontTouchFile) {
182215
fileResize(fd_, size_);
@@ -190,6 +223,13 @@ class MMapFile {
190223
}
191224
}
192225

226+
// Unlock the file if it was locked
227+
if (dev_ != -1ull || ino_ != -1ull) {
228+
fileUnlock(dev_, ino_);
229+
}
230+
dev_ = -1;
231+
ino_ = -1;
232+
193233
size_ = 0;
194234
capacity_ = 0;
195235
filePath_.clear();
@@ -320,6 +360,7 @@ class MMapFile {
320360
uint8_t* data_;
321361
int fd_;
322362
bool readOnly_;
363+
uint64_t dev_, ino_;
323364
};
324365

325366
#endif // MMAPFILE_H

cpp/tests/MMapFileTest.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,14 @@ TEST(MMapFileTest, AppendAndClear) {
4848
TEST(MMapFileTest, ThrowsForInvalidFile) {
4949
ASSERT_THROW(MMapFile("/invalid/path", 1024), std::runtime_error);
5050
}
51+
52+
TEST(MMapFileTest, Locking) {
53+
const std::string filePath = "/tmp/testfile4";
54+
{
55+
// will be closed automatically after the scope
56+
MMapFile file(filePath);
57+
}
58+
MMapFile file2(filePath);
59+
MMapFile file3;
60+
ASSERT_THROW(file3.open(filePath), std::runtime_error);
61+
}

0 commit comments

Comments
 (0)