void CInterProcessLock::Lock(const CTimeout& timeout, const CTimeout& granularity) { CFastMutexGuard LOCK(s_ProcessLock); // Check that lock with specified name not already locked // in the current process. TLocks::iterator it = s_Locks->find(m_SystemName); if (m_Handle != kInvalidLockHandle) { // The lock is already set in this CInterProcessLock object, // just increase reference counter. _VERIFY(it != s_Locks->end()); it->second++; return; } else { if (it != s_Locks->end()) { // The lock already exists in the current process. // We can use one CInterProcessLock object with // multiple Lock() calls, but not with different // CInterProcessLock objects. For example, on MS-Windows, // we cannot wait on the same mutex in the same thread. // So, two different objects can set locks simultaneously. // And for OS-compatibility we can do nothing here, // except throwing an exception. NCBI_THROW(CInterProcessLockException, eMultipleLocks, "Attempt to lock already locked object " \ "in the same process"); } } // Try to acquire a lock with specified timeout #if defined(NCBI_OS_UNIX) // Open lock file mode_t perm = CDirEntry::MakeModeT( CDirEntry::fRead | CDirEntry::fWrite /* user */, CDirEntry::fRead | CDirEntry::fWrite /* group */, 0, 0 /* other & special */); int fd = open(m_SystemName.c_str(), O_CREAT | O_RDWR, perm); if (fd == -1) { NCBI_THROW(CInterProcessLockException, eCreateError, string("Error creating lockfile ") + m_SystemName + ": " + strerror(errno)); } // Try to acquire the lock int x_errno = 0; if (timeout.IsInfinite() || timeout.IsDefault()) { while ((x_errno = s_UnixLock(fd))) { if (errno != EAGAIN) break; } } else { unsigned long ms = timeout.GetAsMilliSeconds(); if ( !ms ) { // Timeout == 0 x_errno = s_UnixLock(fd); } else { // Timeout > 0 unsigned long ms_gran; if ( granularity.IsInfinite() || granularity.IsDefault() ) { ms_gran = min(ms/5, (unsigned long)500); } else { ms_gran = granularity.GetAsMilliSeconds(); } // Try to lock within specified timeout for (;;) { x_errno = s_UnixLock(fd); if ( !x_errno ) { // Successfully locked break; } if (x_errno != EACCES && x_errno != EAGAIN ) { // Error break; } // Otherwise -- sleep granularity timeout unsigned long ms_sleep = ms_gran; if (ms_sleep > ms) { ms_sleep = ms; } if ( !ms_sleep ) { break; } SleepMilliSec(ms_sleep); ms -= ms_sleep; } // Timeout if ( !ms ) { close(fd); NCBI_THROW(CInterProcessLockException, eLockTimeout, "The lock could not be acquired in the time " \ "allotted"); } } // if (!ms) } // if (timeout.IsInfinite()) // Error if ( x_errno ) { close(fd); NCBI_THROW(CInterProcessLockException, eLockError, "Error creating lock"); } // Success m_Handle = fd; #elif defined(NCBI_OS_MSWIN) HANDLE handle = ::CreateMutex(NULL, TRUE, _T_XCSTRING(m_SystemName)); errno_t errcode = ::GetLastError(); if (handle == kInvalidLockHandle) { switch(errcode) { case ERROR_ACCESS_DENIED: // Mutex with specified name already exists, // but we don't have enough rights to open it. NCBI_THROW(CInterProcessLockException, eLockError, "The lock already exists"); break; case ERROR_INVALID_HANDLE: // Some system object with the same name already exists NCBI_THROW(CInterProcessLockException, eLockError, "Error creating lock, system object with the same" \ "name already exists"); break; default: // Unknown error NCBI_THROW(CInterProcessLockException, eCreateError, "Error creating lock"); break; } } else { // Mutex with specified name already exists if (errcode == ERROR_ALREADY_EXISTS) { // Wait DWORD res; if (timeout.IsInfinite() || timeout.IsDefault()) { res = WaitForSingleObject(handle, INFINITE); } else { res = WaitForSingleObject(handle, timeout.GetAsMilliSeconds()); } switch(res) { case WAIT_OBJECT_0: // The lock has been acquired break; case WAIT_TIMEOUT: ::CloseHandle(handle); NCBI_THROW(CInterProcessLockException, eLockTimeout, "The lock could not be acquired in the time " \ "allotted"); break; case WAIT_ABANDONED: // The lock is in abandoned state... Other thread/process // owning it was terminated. We can reuse this mutex, but // it is better to wait until it will be released by OS. /* FALLTHRU */ default: ::CloseHandle(handle); NCBI_THROW(CInterProcessLockException, eLockError, "Error creating lock"); break; } } m_Handle = handle; } #endif // Set reference counter to 1 (*s_Locks)[m_SystemName] = 1; }