CppCommon  1.0.4.1
C++ Common Library
file_lock.cpp
Go to the documentation of this file.
1 
9 #include "threads/file_lock.h"
10 
11 #include "errors/fatal.h"
12 #include "threads/thread.h"
14 
15 #if (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
16 #include <sys/file.h>
17 #include <fcntl.h>
18 #include <unistd.h>
19 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
20 #include <windows.h>
21 #undef Yield
22 #endif
23 
24 namespace CppCommon {
25 
27 
28 // In case we are on a system with glibc version earlier than 2.20
29 #ifndef F_OFD_GETLK
30 #define F_OFD_GETLK 36
31 #define F_OFD_SETLK 37
32 #define F_OFD_SETLKW 38
33 #endif
34 
35 class FileLock::Impl
36 {
37 public:
38 #if (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
39  Impl() : _file(0) {}
40 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
41  Impl() : _file(nullptr) {}
42 #endif
43 
44  ~Impl()
45  {
46  try
47  {
48  Reset();
49  }
50  catch (const SystemException& ex)
51  {
52  fatality(SystemException(ex.string()));
53  }
54  }
55 
56  const Path& path() const noexcept { return _path; }
57 
58  void Assign(const Path& path)
59  {
60  // Reset the previous file-lock
61  Reset();
62 
63  _path = path;
64 #if (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
65  _file = open(_path.string().c_str(), O_CREAT | O_RDWR, 0644);
66  if (_file < 0)
67  throwex FileSystemException("Cannot create or open file-lock file!").Attach(path);
68 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
69  // Retries in CreateFile, see http://support.microsoft.com/kb/316609
70  const std::wstring wpath = _path.wstring();
71  const int attempts = 1000;
72  const int sleep = 100;
73  for (int attempt = 0; attempt < attempts; ++attempt)
74  {
75  _file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
76  if (_file == INVALID_HANDLE_VALUE)
77  {
78  Sleep(sleep);
79  continue;
80  }
81  return;
82  }
83  throwex FileSystemException("Cannot create or open file-lock file!").Attach(path);
84 #endif
85  }
86 
87  void Reset()
88  {
89  if (!_file)
90  return;
91 
92 #if (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
93  int result = close(_file);
94  if (result != 0)
95  fatality(FileSystemException("Cannot close the file-lock descriptor!").Attach(_path));
96  _file = 0;
97 
98  // Remove the file-lock file
99  unlink(_path.string().c_str());
100 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
101  if (!CloseHandle(_file))
102  fatality(FileSystemException("Cannot close the file-lock handle!").Attach(_path));
103  _file = nullptr;
104 #endif
105  }
106 
107  bool TryLockRead()
108  {
109 #if defined(linux) || defined(__linux) || defined(__linux__)
110  struct flock lock;
111  lock.l_type = F_RDLCK;
112  lock.l_whence = SEEK_SET;
113  lock.l_start = 0;
114  lock.l_len = 0;
115  lock.l_pid = 0;
116  int result = fcntl(_file, F_OFD_SETLK, &lock);
117  if (result == -1)
118  {
119  if (errno == EAGAIN)
120  return false;
121  else
122  throwex FileSystemException("Failed to try lock for read!").Attach(_path);
123  }
124  else
125  return true;
126 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
127  int result = flock(_file, LOCK_SH | LOCK_NB);
128  if (result != 0)
129  {
130  if (errno == EWOULDBLOCK)
131  return false;
132  else
133  throwex FileSystemException("Failed to try lock for read!").Attach(_path);
134  }
135  else
136  return true;
137 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
138  OVERLAPPED overlapped;
139  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
140  return LockFileEx(_file, LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD, &overlapped) != 0;
141 #endif
142  }
143 
144  bool TryLockWrite()
145  {
146 #if defined(linux) || defined(__linux) || defined(__linux__)
147  struct flock lock;
148  lock.l_type = F_WRLCK;
149  lock.l_whence = SEEK_SET;
150  lock.l_start = 0;
151  lock.l_len = 0;
152  lock.l_pid = 0;
153  int result = fcntl(_file, F_OFD_SETLK, &lock);
154  if (result == -1)
155  {
156  if (errno == EAGAIN)
157  return false;
158  else
159  throwex FileSystemException("Failed to try lock for write!").Attach(_path);
160  }
161  else
162  return true;
163 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
164  int result = flock(_file, LOCK_EX | LOCK_NB);
165  if (result != 0)
166  {
167  if (errno == EWOULDBLOCK)
168  return false;
169  else
170  throwex FileSystemException("Failed to try lock for write!").Attach(_path);
171  }
172  else
173  return true;
174 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
175  OVERLAPPED overlapped;
176  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
177  return LockFileEx(_file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD, &overlapped) != 0;
178 #endif
179  }
180 
181  void LockRead()
182  {
183 #if defined(linux) || defined(__linux) || defined(__linux__)
184  struct flock lock;
185  lock.l_type = F_RDLCK;
186  lock.l_whence = SEEK_SET;
187  lock.l_start = 0;
188  lock.l_len = 0;
189  lock.l_pid = 0;
190  int result = fcntl(_file, F_OFD_SETLKW, &lock);
191  if (result == -1)
192  throwex FileSystemException("Failed to lock for read!").Attach(_path);
193 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
194  int result = flock(_file, LOCK_SH);
195  if (result != 0)
196  throwex FileSystemException("Failed to lock for read!").Attach(_path);
197 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
198  OVERLAPPED overlapped;
199  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
200  if (!LockFileEx(_file, 0, 0, MAXDWORD, MAXDWORD, &overlapped))
201  throwex FileSystemException("Failed to lock for read!").Attach(_path);
202 #endif
203  }
204 
205  void LockWrite()
206  {
207 #if defined(linux) || defined(__linux) || defined(__linux__)
208  struct flock lock;
209  lock.l_type = F_WRLCK;
210  lock.l_whence = SEEK_SET;
211  lock.l_start = 0;
212  lock.l_len = 0;
213  lock.l_pid = 0;
214  int result = fcntl(_file, F_OFD_SETLKW, &lock);
215  if (result == -1)
216  throwex FileSystemException("Failed to lock for write!").Attach(_path);
217 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
218  int result = flock(_file, LOCK_EX);
219  if (result != 0)
220  throwex FileSystemException("Failed to lock for write!").Attach(_path);
221 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
222  OVERLAPPED overlapped;
223  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
224  if (!LockFileEx(_file, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped))
225  throwex FileSystemException("Failed to lock for write!").Attach(_path);
226 #endif
227  }
228 
229  void UnlockRead()
230  {
231 #if defined(linux) || defined(__linux) || defined(__linux__)
232  struct flock lock;
233  lock.l_type = F_UNLCK;
234  lock.l_whence = SEEK_SET;
235  lock.l_start = 0;
236  lock.l_len = 0;
237  lock.l_pid = 0;
238  int result = fcntl(_file, F_OFD_SETLK, &lock);
239  if (result != 0)
240  throwex FileSystemException("Failed to unlock the read lock!").Attach(_path);
241 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
242  int result = flock(_file, LOCK_UN);
243  if (result != 0)
244  throwex FileSystemException("Failed to unlock the read lock!").Attach(_path);
245 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
246  OVERLAPPED overlapped;
247  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
248  if (!UnlockFileEx(_file, 0, MAXDWORD, MAXDWORD, &overlapped))
249  throwex FileSystemException("Failed to unlock the read lock!").Attach(_path);
250 #endif
251  }
252 
253  void UnlockWrite()
254  {
255 #if defined(linux) || defined(__linux) || defined(__linux__)
256  struct flock lock;
257  lock.l_type = F_UNLCK;
258  lock.l_whence = SEEK_SET;
259  lock.l_start = 0;
260  lock.l_len = 0;
261  lock.l_pid = 0;
262  int result = fcntl(_file, F_OFD_SETLK, &lock);
263  if (result != 0)
264  throwex FileSystemException("Failed to unlock the write lock!").Attach(_path);
265 #elif (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
266  int result = flock(_file, LOCK_UN);
267  if (result != 0)
268  throwex FileSystemException("Failed to unlock the write lock!").Attach(_path);
269 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
270  OVERLAPPED overlapped;
271  ZeroMemory(&overlapped, sizeof(OVERLAPPED));
272  if (!UnlockFileEx(_file, 0, MAXDWORD, MAXDWORD, &overlapped))
273  throwex FileSystemException("Failed to unlock the write lock!").Attach(_path);
274 #endif
275  }
276 
277 private:
278  Path _path;
279 #if (defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)) && !defined(__CYGWIN__)
280  int _file;
281 #elif defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
282  HANDLE _file;
283 #endif
284 };
285 
287 
289 {
290  // Check implementation storage parameters
291  [[maybe_unused]] ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
292  static_assert((StorageSize >= sizeof(Impl)), "FileLock::StorageSize must be increased!");
293  static_assert(((StorageAlign % alignof(Impl)) == 0), "FileLock::StorageAlign must be adjusted!");
294 
295  // Create the implementation instance
296  new(&_storage)Impl();
297 }
298 
300 {
301  // Check implementation storage parameters
302  [[maybe_unused]] ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
303  static_assert((StorageSize >= sizeof(Impl)), "FileLock::StorageSize must be increased!");
304  static_assert(((StorageAlign % alignof(Impl)) == 0), "FileLock::StorageAlign must be adjusted!");
305 
306  // Create the implementation instance
307  new(&_storage)Impl();
308 
309  Assign(path);
310 }
311 
313 {
314  // Delete the implementation instance
315  reinterpret_cast<Impl*>(&_storage)->~Impl();
316 }
317 
319 {
320  Assign(path);
321  return *this;
322 }
323 
324 const Path& FileLock::path() const noexcept { return impl().path(); }
325 
326 void FileLock::Assign(const Path& path) { impl().Assign(path); }
327 void FileLock::Reset() { impl().Reset(); }
328 
329 bool FileLock::TryLockRead() { return impl().TryLockRead(); }
330 bool FileLock::TryLockWrite() { return impl().TryLockWrite(); }
331 
332 bool FileLock::TryLockReadFor(const Timespan& timespan)
333 {
334  // Calculate a finish timestamp
335  Timestamp finish = NanoTimestamp() + timespan;
336 
337  // Try to acquire read lock at least one time
338  if (TryLockRead())
339  return true;
340  else
341  {
342  // Try lock or yield for the given timespan
343  while (NanoTimestamp() < finish)
344  {
345  if (TryLockRead())
346  return true;
347  else
348  Thread::Yield();
349  }
350 
351  // Failed to acquire read lock
352  return false;
353  }
354 }
355 
356 bool FileLock::TryLockWriteFor(const Timespan& timespan)
357 {
358  // Calculate a finish timestamp
359  Timestamp finish = NanoTimestamp() + timespan;
360 
361  // Try to acquire write lock at least one time
362  if (TryLockWrite())
363  return true;
364  else
365  {
366  // Try lock or yield for the given timespan
367  while (NanoTimestamp() < finish)
368  {
369  if (TryLockWrite())
370  return true;
371  else
372  Thread::Yield();
373  }
374 
375  // Failed to acquire write lock
376  return false;
377  }
378 }
379 
380 void FileLock::LockRead() { impl().LockRead(); }
381 void FileLock::LockWrite() { impl().LockWrite(); }
382 void FileLock::UnlockRead() { impl().UnlockRead(); }
383 void FileLock::UnlockWrite() { impl().UnlockWrite(); }
384 
385 } // namespace CppCommon
File-lock synchronization primitive.
Definition: file_lock.h:34
void UnlockRead()
Release read lock.
Definition: file_lock.cpp:382
FileLock & operator=(const Path &path)
Definition: file_lock.cpp:318
bool TryLockWrite()
Try to acquire write lock without block.
Definition: file_lock.cpp:330
void LockRead()
Acquire read lock with block.
Definition: file_lock.cpp:380
bool TryLockReadFor(const Timespan &timespan)
Try to acquire read lock for the given timespan.
Definition: file_lock.cpp:332
const Path & path() const noexcept
Get the file-lock path.
Definition: file_lock.cpp:324
void UnlockWrite()
Release write lock.
Definition: file_lock.cpp:383
bool TryLockRead()
Try to acquire read lock without block.
Definition: file_lock.cpp:329
void Reset()
Reset file-lock.
Definition: file_lock.cpp:327
bool TryLockWriteFor(const Timespan &timespan)
Try to acquire write lock for the given timespan.
Definition: file_lock.cpp:356
void Assign(const Path &path)
Assign a new file-lock path.
Definition: file_lock.cpp:326
void LockWrite()
Acquire write lock with block.
Definition: file_lock.cpp:381
High resolution timestamp.
Definition: timestamp.h:272
Filesystem path.
Definition: path.h:90
static void Yield() noexcept
Yield to other threads.
Definition: thread.cpp:143
#define throwex
Throw extended exception macro.
Definition: exceptions.h:23
Fatal abort execution definition.
#define fatality(...)
Fatal abort execution extended macro.
Definition: fatal.h:22
File-lock synchronization primitive definition.
C++ Common project definitions.
Definition: token_bucket.h:15
Thread definition.
Aligned storage validator definition.