NCBI C++ ToolKit
interprocess_lock.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: interprocess_lock.cpp 90917 2020-08-07 22:21:56Z lavr $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Vladimir Ivanov
27  *
28  *
29  */
30 
31 #include <ncbi_pch.hpp>
32 #include <corelib/ncbifile.hpp>
33 #include <corelib/ncbi_system.hpp>
35 #include "ncbisys.hpp"
36 #include <map>
37 
38 #if defined(NCBI_OS_UNIX)
39 # include <errno.h>
40 # include <sys/types.h>
41 # include <sys/stat.h>
42 # include <unistd.h>
43 # include <fcntl.h>
44 #elif defined(NCBI_OS_MSWIN)
45 # include <windows.h>
46 #endif
47 
48 
50 
51 /// System specific invalid lock handle.
52 #if defined(NCBI_OS_UNIX)
53  const int kInvalidLockHandle = -1;
54 #elif defined(NCBI_OS_MSWIN)
56 #endif
57 
58 // List of all locks in the current process <name, ref_counter>.
61 
62 // Protective mutex for save access to s_Locks in MT environment.
63 DEFINE_STATIC_FAST_MUTEX(s_ProcessLock);
64 
65 
66 
67 //////////////////////////////////////////////////////////////////////////////
68 //
69 // CInterProcessLock
70 //
71 
73  : m_Name(name)
74 {
76 
77 #if defined(NCBI_OS_UNIX)
80  } else {
81  if (m_Name.find("/") == NPOS) {
82  m_SystemName = "/var/tmp/" + m_Name;
83  }
84  }
85 #elif defined(NCBI_OS_MSWIN)
86 
87  // Backslash is not allowed in the mutex name
88  m_SystemName = NStr::Replace(m_Name, "\\", "/");
89 
90 #endif
91  if ( m_SystemName.empty() ||
92  m_SystemName.length() > PATH_MAX) {
94  "Incorrect name for the lock");
95  }
96 }
97 
98 
100 {
101  if (m_Handle != kInvalidLockHandle) {
102  try {
103  Unlock();
104  }
105  catch (exception&) {}
106  }
107 }
108 
109 
110 #if defined(NCBI_OS_UNIX)
111 
112 /// Try to acquire a lock for specified file descriptor.
113 /// Return errno on error, or 0 on success.
114 static int s_UnixLock(int fd)
115 {
116  int x_errno = 0;
117 # if defined(F_TLOCK)
118  if ( lockf(fd, F_TLOCK, 0) < 0) {
119  x_errno = errno;
120  }
121 # elif defined(F_SETLK)
122  struct flock lockparam;
123  lockparam.l_type = F_WRLCK;
124  lockparam.l_whence = SEEK_SET;
125  lockparam.l_start = 0;
126  lockparam.l_len = 0; /* whole file */
127  while (fcntl(fd, F_SETLK, &lockparam) < 0) {
128  x_errno = errno;
129  if (x_errno != EINTR) {
130  break;
131  }
132  }
133 # else
134 # error "No supported lock method. Please port this code."
135 # endif
136  return x_errno;
137 }
138 
139 #endif
140 
141 
142 void CInterProcessLock::Lock(const CTimeout& timeout,
143  const CTimeout& granularity)
144 {
145  CFastMutexGuard LOCK(s_ProcessLock);
146 
147  // Check that lock with specified name not already locked
148  // in the current process.
150 
151  if (m_Handle != kInvalidLockHandle) {
152  // The lock is already set in this CInterProcessLock object,
153  // just increase reference counter.
154  _VERIFY(it != s_Locks->end());
155  it->second++;
156  return;
157  } else {
158  if (it != s_Locks->end()) {
159  // The lock already exists in the current process.
160  // We can use one CInterProcessLock object with
161  // multiple Lock() calls, but not with different
162  // CInterProcessLock objects. For example, on MS-Windows,
163  // we cannot wait on the same mutex in the same thread.
164  // So, two different objects can set locks simultaneously.
165  // And for OS-compatibility we can do nothing here,
166  // except throwing an exception.
167  NCBI_THROW(CInterProcessLockException, eMultipleLocks,
168  "Attempt to lock already locked object " \
169  "in the same process");
170  }
171  }
172 
173  // Try to acquire a lock with specified timeout
174 
175 #if defined(NCBI_OS_UNIX)
176 
177  // Open lock file
179  CDirEntry::fRead | CDirEntry::fWrite /* user */,
180  CDirEntry::fRead | CDirEntry::fWrite /* group */,
181  0, 0 /* other & special */);
182  int fd = open(m_SystemName.c_str(), O_CREAT | O_RDWR, perm);
183  if (fd == -1) {
185  string("Error creating lockfile ") + m_SystemName +
186  ": " + strerror(errno));
187  }
188 
189  // Try to acquire the lock
190 
191  int x_errno = 0;
192 
193  if (timeout.IsInfinite() || timeout.IsDefault()) {
194  while ((x_errno = s_UnixLock(fd))) {
195  if (errno != EAGAIN)
196  break;
197  }
198 
199  } else {
200  unsigned long ms = timeout.GetAsMilliSeconds();
201  if ( !ms ) {
202  // Timeout == 0
203  x_errno = s_UnixLock(fd);
204  } else {
205  // Timeout > 0
206  unsigned long ms_gran;
207  if ( granularity.IsInfinite() ||
208  granularity.IsDefault() )
209  {
210  ms_gran = min(ms/5, (unsigned long)500);
211  } else {
212  ms_gran = granularity.GetAsMilliSeconds();
213  }
214  // Try to lock within specified timeout
215  for (;;) {
216  x_errno = s_UnixLock(fd);
217  if ( !x_errno ) {
218  // Successfully locked
219  break;
220  }
221  if (x_errno != EACCES &&
222  x_errno != EAGAIN ) {
223  // Error
224  break;
225  }
226  // Otherwise -- sleep granularity timeout
227  unsigned long ms_sleep = ms_gran;
228  if (ms_sleep > ms) {
229  ms_sleep = ms;
230  }
231  if ( !ms_sleep ) {
232  break;
233  }
234  SleepMilliSec(ms_sleep);
235  ms -= ms_sleep;
236  }
237  // Timeout
238  if ( !ms ) {
239  close(fd);
241  "The lock could not be acquired in the time " \
242  "allotted");
243  }
244  } // if (!ms)
245  } // if (timeout.IsInfinite())
246 
247  // Error
248  if ( x_errno ) {
249  close(fd);
251  "Error creating lock");
252  }
253  // Success
254  m_Handle = fd;
255 
256 #elif defined(NCBI_OS_MSWIN)
257 
258  HANDLE handle = ::CreateMutex(NULL, TRUE, _T_XCSTRING(m_SystemName));
259  errno_t errcode = ::GetLastError();
260  if (handle == kInvalidLockHandle) {
261  switch(errcode) {
262  case ERROR_ACCESS_DENIED:
263  // Mutex with specified name already exists,
264  // but we don't have enough rights to open it.
266  "The lock already exists");
267  break;
268  case ERROR_INVALID_HANDLE:
269  // Some system object with the same name already exists
271  "Error creating lock, system object with the same" \
272  "name already exists");
273  break;
274  default:
275  // Unknown error
277  "Error creating lock");
278  break;
279  }
280  } else {
281  // Mutex with specified name already exists
282  if (errcode == ERROR_ALREADY_EXISTS) {
283  // Wait
284  DWORD res;
285  if (timeout.IsInfinite() || timeout.IsDefault()) {
286  res = WaitForSingleObject(handle, INFINITE);
287  } else {
288  res = WaitForSingleObject(handle, timeout.GetAsMilliSeconds());
289  }
290  switch(res) {
291  case WAIT_OBJECT_0:
292  // The lock has been acquired
293  break;
294  case WAIT_TIMEOUT:
295  ::CloseHandle(handle);
297  "The lock could not be acquired in the time " \
298  "allotted");
299  break;
300  case WAIT_ABANDONED:
301  // The lock is in abandoned state... Other thread/process
302  // owning it was terminated. We can reuse this mutex, but
303  // it is better to wait until it will be released by OS.
304  /*FALLTHRU*/
305  default:
306  ::CloseHandle(handle);
308  "Error creating lock");
309  break;
310  }
311  }
312  m_Handle = handle;
313  }
314 #endif
315  // Set reference counter to 1
316  (*s_Locks)[m_SystemName] = 1;
317 }
318 
319 
321 {
322  if (m_Handle == kInvalidLockHandle) {
324  "Attempt to unlock not-yet-acquired lock");
325  }
326  CFastMutexGuard LOCK(s_ProcessLock);
327 
328  // Check that lock with specified name not already locked
329  // in the current process.
331  _VERIFY(it != s_Locks->end());
332 
333  if ( it->second > 1 ) {
334  // Just decrease reference counter
335  it->second--;
336  return;
337  }
338 
339  // Release lock
340 
341 #if defined(NCBI_OS_UNIX)
342 
343 # if defined(F_TLOCK)
344  int res = lockf(m_Handle, F_ULOCK, 0);
345 # elif defined(F_SETLK)
346  struct flock lockparam;
347  lockparam.l_type = F_UNLCK;
348  lockparam.l_whence = SEEK_SET;
349  lockparam.l_start = 0;
350  lockparam.l_len = 0; /* whole file */
351  int res = fcntl(m_Handle, F_SETLK, &lockparam);
352 # else
353 # error "No supported lock method. Please port this code."
354 # endif
355  if ( res < 0 ) {
357  "Cannot release the lock");
358  }
359  close(m_Handle);
360 
361 #elif defined(NCBI_OS_MSWIN)
362  if ( !::ReleaseMutex(m_Handle) ) {
364  "Cannot release the lock");
365  }
366  ::CloseHandle(m_Handle);
367 #endif
369  s_Locks->erase(m_SystemName);
370 }
371 
372 
374 {
375  if (m_Handle != kInvalidLockHandle) {
376  Unlock();
377  }
379 }
380 
381 
383 {
384  try {
385  Lock(CTimeout(0,0));
386  }
387  catch (CInterProcessLockException&) {
388  return false;
389  }
390  return true;
391 }
392 
393 
394 
395 //////////////////////////////////////////////////////////////////////////////
396 //
397 // CInterProcessLockException
398 //
399 
401 {
402  switch (GetErrCode()) {
403  case eLockTimeout: return "eLockTimeout";
404  case eCreateError: return "eCreateError";
405  case eLockError: return "eLockError";
406  case eUnlockError: return "eUnlockError";
407  case eMultipleLocks: return "eMultipleLocks";
408  case eNotLocked: return "eNotLocked";
409  default: return CException::GetErrCodeString();
410  }
411 }
412 
413 
CInterProcessLockException –.
CSafeStatic<>::
CTimeout – Timeout interval.
Definition: ncbitime.hpp:1693
int close(int fd)
Definition: connection.cpp:45
#define NULL
Definition: ncbistd.hpp:225
#define _VERIFY(expr)
Definition: ncbidbg.hpp:161
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string.
Definition: ncbiexpt.hpp:704
TErrCode GetErrCode(void) const
Definition: ncbiexpt.hpp:1493
virtual const char * GetErrCodeString(void) const
Get error code interpreted as text.
Definition: ncbiexpt.cpp:444
static bool IsAbsolutePath(const string &path)
Check if a "path" is absolute for the current OS.
Definition: ncbifile.cpp:508
static mode_t MakeModeT(TMode user_mode, TMode group_mode, TMode other_mode, TSpecialModeBits special)
Construct mode_t value from permission modes.
Definition: ncbifile.cpp:1193
unsigned int mode_t
Definition: ncbifile.hpp:84
#define PATH_MAX
Definition: ncbifile.hpp:106
@ fRead
Read permission.
Definition: ncbifile.hpp:1154
@ fWrite
Write permission.
Definition: ncbifile.hpp:1153
string m_SystemName
Adjusted name of the lock.
bool TryLock(void)
Try to acquire the lock.
void Unlock(void)
Release the lock.
~CInterProcessLock(void)
Call Unlock()
void Lock(const CTimeout &timeout=CTimeout(CTimeout::eInfinite), const CTimeout &granularity=CTimeout(CTimeout::eInfinite))
CInterProcessLock(const string &name)
void Remove(void)
Call Unlock() and removes lock object from the system.
string m_Name
Original name of the lock.
virtual const char * GetErrCodeString(void) const override
Translate from an error code value to its string representation.
@ eUnlockError
Cannot release the lock.
@ eLockError
Cannot acquire a lock (not eLockTimeout, eCreateError)
@ eLockTimeout
The lock could not be acquired in the time allotted.
@ eNotLocked
Attempt to unlock a not-yet-acquired lock.
@ eMultipleLocks
Attempt to lock already locked object in the same process.
@ eCreateError
Cannot create the lock object in the OS.
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
#define NPOS
Definition: ncbistr.hpp:133
static string & Replace(const string &src, const string &search, const string &replace, string &dst, SIZE_TYPE start_pos=0, SIZE_TYPE max_replace=0, SIZE_TYPE *num_replace=0)
Replace occurrences of a substring within a string.
Definition: ncbistr.cpp:3305
#define _T_XCSTRING(x)
Definition: ncbistr.hpp:181
bool IsInfinite() const
Definition: ncbitime.hpp:2729
unsigned long GetAsMilliSeconds(void) const
Get as number of milliseconds.
Definition: ncbitime.cpp:3490
bool IsDefault() const
Definition: ncbitime.hpp:2723
#define HANDLE
An abstraction for a file handle.
Definition: mdb.c:383
static CSafeStatic< TLocks > s_Locks
DEFINE_STATIC_FAST_MUTEX(s_ProcessLock)
static int s_UnixLock(int fd)
Try to acquire a lock for specified file descriptor.
const int kInvalidLockHandle
System specific invalid lock handle.
map< string, int > TLocks
Simple inter-process lock.
#define TRUE
bool replacment for C indicating true.
Definition: ncbi_std.h:97
void SleepMilliSec(unsigned long ml_sec, EInterruptOnSignal onsignal=eRestartOnSignal)
Defines classes: CDirEntry, CFile, CDir, CSymLink, CMemoryFile, CFileUtil, CFileLock,...
T min(T x_, T y_)
char * strerror(int n)
Definition: pcre2grep.c:1177
static SLJIT_INLINE sljit_ins ms(sljit_gpr r, sljit_s32 d, sljit_gpr x, sljit_gpr b)
unsigned int DWORD
Definition: sqltypes.h:98
#define NcbiSys_unlink
Definition: ncbisys.hpp:109
#define SEEK_SET
Definition: zconf.h:500
Modified on Fri Sep 20 14:58:11 2024 by modify_doxy.py rev. 669887