1 /*  $Id: interprocess_lock.cpp 613504 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>
34 #include <corelib/interprocess_lock.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 
49 BEGIN_NCBI_SCOPE
50 
51 /// System specific invalid lock handle.
52 #if   defined(NCBI_OS_UNIX)
53     const int    kInvalidLockHandle = -1;
54 #elif defined(NCBI_OS_MSWIN)
55     const HANDLE kInvalidLockHandle = NULL;
56 #endif
57 
58 // List of all locks in the current process <name, ref_counter>.
59 typedef map<string, int> TLocks;
60 static CSafeStatic<TLocks> s_Locks;
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 
CInterProcessLock(const string & name)72 CInterProcessLock::CInterProcessLock(const string& name)
73     : m_Name(name)
74 {
75     m_Handle = kInvalidLockHandle;
76 
77 #if defined(NCBI_OS_UNIX)
78     if ( CFile::IsAbsolutePath(m_Name) ) {
79         m_SystemName = m_Name;
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) {
93         NCBI_THROW(CInterProcessLockException, eNameError,
94                    "Incorrect name for the lock");
95     }
96 }
97 
98 
~CInterProcessLock()99 CInterProcessLock::~CInterProcessLock()
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.
s_UnixLock(int fd)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 
Lock(const CTimeout & timeout,const CTimeout & granularity)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.
149     TLocks::iterator it = s_Locks->find(m_SystemName);
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
178     mode_t perm = CDirEntry::MakeModeT(
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) {
184         NCBI_THROW(CInterProcessLockException, eCreateError,
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);
240                 NCBI_THROW(CInterProcessLockException, eLockTimeout,
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);
250         NCBI_THROW(CInterProcessLockException, eLockError,
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.
265                 NCBI_THROW(CInterProcessLockException, eLockError,
266                            "The lock already exists");
267                 break;
268             case ERROR_INVALID_HANDLE:
269                 // Some system object with the same name already exists
270                 NCBI_THROW(CInterProcessLockException, eLockError,
271                            "Error creating lock, system object with the same" \
272                            "name already exists");
273                 break;
274             default:
275                 // Unknown error
276                 NCBI_THROW(CInterProcessLockException, eCreateError,
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);
296                     NCBI_THROW(CInterProcessLockException, eLockTimeout,
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);
307                     NCBI_THROW(CInterProcessLockException, eLockError,
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 
Unlock()320 void CInterProcessLock::Unlock()
321 {
322     if (m_Handle == kInvalidLockHandle) {
323         NCBI_THROW(CInterProcessLockException, eNotLocked,
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.
330     TLocks::iterator it = s_Locks->find(m_SystemName);
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 ) {
356         NCBI_THROW(CInterProcessLockException, eUnlockError,
357                    "Cannot release the lock");
358     }
359     close(m_Handle);
360 
361 #elif defined(NCBI_OS_MSWIN)
362     if ( !::ReleaseMutex(m_Handle) ) {
363         NCBI_THROW(CInterProcessLockException, eUnlockError,
364                    "Cannot release the lock");
365     }
366     ::CloseHandle(m_Handle);
367 #endif
368     m_Handle = kInvalidLockHandle;
369     s_Locks->erase(m_SystemName);
370 }
371 
372 
Remove()373 void CInterProcessLock::Remove()
374 {
375     if (m_Handle != kInvalidLockHandle) {
376         Unlock();
377     }
378     NcbiSys_unlink(_T_XCSTRING(m_SystemName));
379 }
380 
381 
TryLock()382 bool CInterProcessLock::TryLock()
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 
GetErrCodeString(void) const400 const char* CInterProcessLockException::GetErrCodeString(void) const
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 
414 END_NCBI_SCOPE
415