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