summaryrefslogtreecommitdiff
path: root/src/pal/src/synchobj/mutex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pal/src/synchobj/mutex.cpp')
-rw-r--r--src/pal/src/synchobj/mutex.cpp1584
1 files changed, 1584 insertions, 0 deletions
diff --git a/src/pal/src/synchobj/mutex.cpp b/src/pal/src/synchobj/mutex.cpp
new file mode 100644
index 0000000000..d929eaa472
--- /dev/null
+++ b/src/pal/src/synchobj/mutex.cpp
@@ -0,0 +1,1584 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*++
+
+
+
+Module Name:
+
+ mutex.ccpp
+
+Abstract:
+
+ Implementation of mutex synchroniztion object as described in
+ the WIN32 API
+
+Revision History:
+
+
+
+--*/
+
+#include "pal/dbgmsg.h"
+
+SET_DEFAULT_DEBUG_CHANNEL(SYNC); // some headers have code with asserts, so do this first
+
+#include "pal/mutex.hpp"
+#include "pal/file.hpp"
+#include "pal/thread.hpp"
+
+#include "../synchmgr/synchmanager.hpp"
+
+#include <sys/file.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "pal/sharedmemory.inl"
+
+using namespace CorUnix;
+
+/* ------------------- Definitions ------------------------------*/
+
+CObjectType CorUnix::otMutex(
+ otiMutex,
+ NULL, // No cleanup routine
+ NULL, // No initialization routine
+ 0, // No immutable data
+ 0, // No process local data
+ 0, // No shared data
+ 0, // Should be MUTEX_ALL_ACCESS; currently ignored (no Win32 security)
+ CObjectType::SecuritySupported,
+ CObjectType::SecurityInfoNotPersisted,
+ CObjectType::UnnamedObject,
+ CObjectType::LocalDuplicationOnly,
+ CObjectType::WaitableObject,
+ CObjectType::ObjectCanBeUnsignaled,
+ CObjectType::ThreadReleaseAltersSignalCount,
+ CObjectType::OwnershipTracked
+ );
+
+static CAllowedObjectTypes aotMutex(otiMutex);
+
+CObjectType CorUnix::otNamedMutex(
+ otiNamedMutex,
+ &SharedMemoryProcessDataHeader::PalObject_Close, // Cleanup routine
+ NULL, // No initialization routine
+ sizeof(SharedMemoryProcessDataHeader *), // Immutable data
+ 0, // No process local data
+ 0, // No shared data
+ 0, // Should be MUTEX_ALL_ACCESS; currently ignored (no Win32 security)
+ CObjectType::SecuritySupported,
+ CObjectType::SecurityInfoNotPersisted,
+ CObjectType::UnnamedObject, // PAL's naming infrastructure is not used
+ CObjectType::LocalDuplicationOnly,
+ CObjectType::UnwaitableObject, // PAL's waiting infrastructure is not used
+ CObjectType::SignalingNotApplicable, // PAL's signaling infrastructure is not used
+ CObjectType::ThreadReleaseNotApplicable, // PAL's signaling infrastructure is not used
+ CObjectType::OwnershipNotApplicable // PAL's ownership infrastructure is not used
+ );
+
+static CAllowedObjectTypes aotNamedMutex(otiNamedMutex);
+
+static PalObjectTypeId anyMutexTypeIds[] = {otiMutex, otiNamedMutex};
+static CAllowedObjectTypes aotAnyMutex(anyMutexTypeIds, _countof(anyMutexTypeIds));
+
+/*++
+Function:
+ CreateMutexA
+
+Note:
+ lpMutexAttributes currentely ignored:
+ -- Win32 object security not supported
+ -- handles to mutex objects are not inheritable
+
+Parameters:
+ See MSDN doc.
+--*/
+
+HANDLE
+PALAPI
+CreateMutexA(
+ IN LPSECURITY_ATTRIBUTES lpMutexAttributes,
+ IN BOOL bInitialOwner,
+ IN LPCSTR lpName)
+{
+ HANDLE hMutex = NULL;
+ CPalThread *pthr = NULL;
+ PAL_ERROR palError;
+
+ PERF_ENTRY(CreateMutexA);
+ ENTRY("CreateMutexA(lpMutexAttr=%p, bInitialOwner=%d, lpName=%p (%s)\n",
+ lpMutexAttributes, bInitialOwner, lpName, lpName?lpName:"NULL");
+
+ pthr = InternalGetCurrentThread();
+
+ palError = InternalCreateMutex(
+ pthr,
+ lpMutexAttributes,
+ bInitialOwner,
+ lpName,
+ &hMutex
+ );
+
+ //
+ // We always need to set last error, even on success:
+ // we need to protect ourselves from the situation
+ // where last error is set to ERROR_ALREADY_EXISTS on
+ // entry to the function
+ //
+
+ pthr->SetLastError(palError);
+
+ LOGEXIT("CreateMutexA returns HANDLE %p\n", hMutex);
+ PERF_EXIT(CreateMutexA);
+ return hMutex;
+}
+
+
+/*++
+Function:
+ CreateMutexW
+
+Note:
+ lpMutexAttributes currentely ignored:
+ -- Win32 object security not supported
+ -- handles to mutex objects are not inheritable
+
+Parameters:
+ See MSDN doc.
+--*/
+
+HANDLE
+PALAPI
+CreateMutexW(
+ IN LPSECURITY_ATTRIBUTES lpMutexAttributes,
+ IN BOOL bInitialOwner,
+ IN LPCWSTR lpName)
+{
+ HANDLE hMutex = NULL;
+ PAL_ERROR palError;
+ CPalThread *pthr = NULL;
+ char utf8Name[SHARED_MEMORY_MAX_NAME_CHAR_COUNT + 1];
+
+ PERF_ENTRY(CreateMutexW);
+ ENTRY("CreateMutexW(lpMutexAttr=%p, bInitialOwner=%d, lpName=%p (%S)\n",
+ lpMutexAttributes, bInitialOwner, lpName, lpName?lpName:W16_NULLSTRING);
+
+ pthr = InternalGetCurrentThread();
+
+ if (lpName != nullptr)
+ {
+ int bytesWritten = WideCharToMultiByte(CP_ACP, 0, lpName, -1, utf8Name, _countof(utf8Name), nullptr, nullptr);
+ if (bytesWritten == 0)
+ {
+ DWORD errorCode = GetLastError();
+ if (errorCode == ERROR_INSUFFICIENT_BUFFER)
+ {
+ palError = static_cast<DWORD>(SharedMemoryError::NameTooLong);
+ }
+ else
+ {
+ ASSERT("WideCharToMultiByte failed (%u)\n", errorCode);
+ palError = errorCode;
+ }
+ goto CreateMutexWExit;
+ }
+ }
+
+ palError = InternalCreateMutex(
+ pthr,
+ lpMutexAttributes,
+ bInitialOwner,
+ lpName == nullptr ? nullptr : utf8Name,
+ &hMutex
+ );
+
+CreateMutexWExit:
+ //
+ // We always need to set last error, even on success:
+ // we need to protect ourselves from the situation
+ // where last error is set to ERROR_ALREADY_EXISTS on
+ // entry to the function
+ //
+
+ pthr->SetLastError(palError);
+
+ LOGEXIT("CreateMutexW returns HANDLE %p\n", hMutex);
+ PERF_EXIT(CreateMutexW);
+ return hMutex;
+}
+
+/*++
+Function:
+ InternalCreateMutex
+
+Note:
+ lpMutexAttributes currentely ignored:
+ -- Win32 object security not supported
+ -- handles to mutex objects are not inheritable
+
+Parameters:
+ pthr -- thread data for calling thread
+ phEvent -- on success, receives the allocated mutex handle
+
+ See MSDN docs on CreateMutex for all other parameters
+--*/
+
+PAL_ERROR
+CorUnix::InternalCreateMutex(
+ CPalThread *pthr,
+ LPSECURITY_ATTRIBUTES lpMutexAttributes,
+ BOOL bInitialOwner,
+ LPCSTR lpName,
+ HANDLE *phMutex
+ )
+{
+ CObjectAttributes oa(nullptr, lpMutexAttributes);
+ PAL_ERROR palError = NO_ERROR;
+ IPalObject *pobjMutex = NULL;
+ IPalObject *pobjRegisteredMutex = NULL;
+ ISynchStateController *pssc = NULL;
+ HANDLE hMutex = nullptr;
+
+ _ASSERTE(NULL != pthr);
+ _ASSERTE(NULL != phMutex);
+
+ ENTRY("InternalCreateMutex(pthr=%p, lpMutexAttributes=%p, bInitialOwner=%d"
+ ", lpName=%p, phMutex=%p)\n",
+ pthr,
+ lpMutexAttributes,
+ bInitialOwner,
+ lpName,
+ phMutex
+ );
+
+ if (lpName != nullptr && lpName[0] == '\0')
+ {
+ // Empty name is treated as a request for an unnamed process-local mutex
+ lpName = nullptr;
+ }
+
+ CObjectType *ot = lpName == nullptr ? &otMutex : &otNamedMutex;
+ CAllowedObjectTypes *aot = lpName == nullptr ? &aotMutex : &aotNamedMutex;
+
+ palError = g_pObjectManager->AllocateObject(
+ pthr,
+ ot,
+ &oa,
+ &pobjMutex
+ );
+
+ if (NO_ERROR != palError)
+ {
+ goto InternalCreateMutexExit;
+ }
+
+ if (lpName == nullptr)
+ {
+ palError = pobjMutex->GetSynchStateController(
+ pthr,
+ &pssc
+ );
+
+ if (NO_ERROR != palError)
+ {
+ ASSERT("Unable to create state controller (%d)\n", palError);
+ goto InternalCreateMutexExit;
+ }
+
+ if (bInitialOwner)
+ {
+ palError = pssc->SetOwner(pthr);
+ }
+ else
+ {
+ palError = pssc->SetSignalCount(1);
+ }
+
+ pssc->ReleaseController();
+
+ if (NO_ERROR != palError)
+ {
+ ASSERT("Unable to set initial mutex state (%d)\n", palError);
+ goto InternalCreateMutexExit;
+ }
+ }
+
+ palError = g_pObjectManager->RegisterObject(
+ pthr,
+ pobjMutex,
+ aot,
+ 0, // should be MUTEX_ALL_ACCESS -- currently ignored (no Win32 security)
+ &hMutex,
+ &pobjRegisteredMutex
+ );
+
+ if (palError != NO_ERROR)
+ {
+ _ASSERTE(palError != ERROR_ALREADY_EXISTS); // PAL's naming infrastructure is not used for named mutexes
+ _ASSERTE(pobjRegisteredMutex == nullptr);
+ _ASSERTE(hMutex == nullptr);
+ goto InternalCreateMutexExit;
+ }
+
+ // Now that the object has been registered successfully, it would have a reference associated with the handle, so release
+ // the initial reference. Any errors from now on need to revoke the handle.
+ _ASSERTE(pobjRegisteredMutex == pobjMutex);
+ _ASSERTE(hMutex != nullptr);
+ pobjMutex->ReleaseReference(pthr);
+ pobjRegisteredMutex = nullptr;
+
+ if (lpName != nullptr)
+ {
+ SharedMemoryProcessDataHeader *processDataHeader;
+ bool created = false;
+ try
+ {
+ processDataHeader = NamedMutexProcessData::CreateOrOpen(lpName, !!bInitialOwner, &created);
+ }
+ catch (SharedMemoryException ex)
+ {
+ palError = ex.GetErrorCode();
+ goto InternalCreateMutexExit;
+ }
+ SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(pobjMutex, processDataHeader);
+
+ if (!created)
+ {
+ // Indicate to the caller that an existing mutex was opened, and hence the caller will not have initial ownership
+ // of the mutex if requested through bInitialOwner
+ palError = ERROR_ALREADY_EXISTS;
+ }
+ }
+
+ *phMutex = hMutex;
+ hMutex = nullptr;
+ pobjMutex = nullptr;
+
+InternalCreateMutexExit:
+
+ _ASSERTE(pobjRegisteredMutex == nullptr);
+ if (hMutex != nullptr)
+ {
+ g_pObjectManager->RevokeHandle(pthr, hMutex);
+ }
+ else if (NULL != pobjMutex)
+ {
+ pobjMutex->ReleaseReference(pthr);
+ }
+
+ LOGEXIT("InternalCreateMutex returns %i\n", palError);
+
+ return palError;
+}
+
+/*++
+Function:
+ ReleaseMutex
+
+Parameters:
+ See MSDN doc.
+--*/
+
+BOOL
+PALAPI
+ReleaseMutex( IN HANDLE hMutex )
+{
+ PAL_ERROR palError = NO_ERROR;
+ CPalThread *pthr = NULL;
+
+ PERF_ENTRY(ReleaseMutex);
+ ENTRY("ReleaseMutex(hMutex=%p)\n", hMutex);
+
+ pthr = InternalGetCurrentThread();
+
+ palError = InternalReleaseMutex(pthr, hMutex);
+
+ if (NO_ERROR != palError)
+ {
+ pthr->SetLastError(palError);
+ }
+
+ LOGEXIT("ReleaseMutex returns BOOL %d\n", (NO_ERROR == palError));
+ PERF_EXIT(ReleaseMutex);
+ return (NO_ERROR == palError);
+}
+
+/*++
+Function:
+ InternalReleaseMutex
+
+Parameters:
+ pthr -- thread data for calling thread
+
+ See MSDN docs on ReleaseMutex for all other parameters
+--*/
+
+PAL_ERROR
+CorUnix::InternalReleaseMutex(
+ CPalThread *pthr,
+ HANDLE hMutex
+ )
+{
+ PAL_ERROR palError = NO_ERROR;
+ IPalObject *pobjMutex = NULL;
+ ISynchStateController *pssc = NULL;
+ PalObjectTypeId objectTypeId;
+
+ _ASSERTE(NULL != pthr);
+
+ ENTRY("InternalReleaseMutex(pthr=%p, hMutex=%p)\n",
+ pthr,
+ hMutex
+ );
+
+ palError = g_pObjectManager->ReferenceObjectByHandle(
+ pthr,
+ hMutex,
+ &aotAnyMutex,
+ 0, // should be MUTEX_MODIFY_STATE -- current ignored (no Win32 security)
+ &pobjMutex
+ );
+
+ if (NO_ERROR != palError)
+ {
+ ERROR("Unable to obtain object for handle %p (error %d)!\n", hMutex, palError);
+ goto InternalReleaseMutexExit;
+ }
+
+ objectTypeId = pobjMutex->GetObjectType()->GetId();
+ if (objectTypeId == otiMutex)
+ {
+ palError = pobjMutex->GetSynchStateController(
+ pthr,
+ &pssc
+ );
+
+ if (NO_ERROR != palError)
+ {
+ ASSERT("Error %d obtaining synch state controller\n", palError);
+ goto InternalReleaseMutexExit;
+ }
+
+ palError = pssc->DecrementOwnershipCount();
+
+ if (NO_ERROR != palError)
+ {
+ ERROR("Error %d decrementing mutex ownership count\n", palError);
+ goto InternalReleaseMutexExit;
+ }
+ }
+ else
+ {
+ _ASSERTE(objectTypeId == otiNamedMutex);
+
+ SharedMemoryProcessDataHeader *processDataHeader =
+ SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(pobjMutex);
+ _ASSERTE(processDataHeader != nullptr);
+ try
+ {
+ static_cast<NamedMutexProcessData *>(processDataHeader->GetData())->ReleaseLock();
+ }
+ catch (SharedMemoryException ex)
+ {
+ palError = ex.GetErrorCode();
+ goto InternalReleaseMutexExit;
+ }
+ }
+
+InternalReleaseMutexExit:
+
+ if (NULL != pssc)
+ {
+ pssc->ReleaseController();
+ }
+
+ if (NULL != pobjMutex)
+ {
+ pobjMutex->ReleaseReference(pthr);
+ }
+
+ LOGEXIT("InternalReleaseMutex returns %i\n", palError);
+
+ return palError;
+}
+
+/*++
+Function:
+ OpenMutexA
+
+Note:
+ dwDesiredAccess is currently ignored (no Win32 object security support)
+ bInheritHandle is currently ignored (handles to mutexes are not inheritable)
+
+See MSDN doc.
+--*/
+
+HANDLE
+PALAPI
+OpenMutexA (
+ IN DWORD dwDesiredAccess,
+ IN BOOL bInheritHandle,
+ IN LPCSTR lpName)
+{
+ HANDLE hMutex = NULL;
+ CPalThread *pthr = NULL;
+ PAL_ERROR palError;
+
+ PERF_ENTRY(OpenMutexA);
+ ENTRY("OpenMutexA(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%s))\n",
+ dwDesiredAccess, bInheritHandle, lpName, lpName?lpName:"NULL");
+
+ pthr = InternalGetCurrentThread();
+
+ /* validate parameters */
+ if (lpName == nullptr)
+ {
+ ERROR("name is NULL\n");
+ palError = ERROR_INVALID_PARAMETER;
+ goto OpenMutexAExit;
+ }
+
+ palError = InternalOpenMutex(pthr, dwDesiredAccess, bInheritHandle, lpName, &hMutex);
+
+OpenMutexAExit:
+ if (NO_ERROR != palError)
+ {
+ pthr->SetLastError(palError);
+ }
+
+ LOGEXIT("OpenMutexA returns HANDLE %p\n", hMutex);
+ PERF_EXIT(OpenMutexA);
+ return hMutex;
+}
+
+/*++
+Function:
+ OpenMutexW
+
+Note:
+ dwDesiredAccess is currently ignored (no Win32 object security support)
+ bInheritHandle is currently ignored (handles to mutexes are not inheritable)
+
+See MSDN doc.
+--*/
+
+HANDLE
+PALAPI
+OpenMutexW(
+ IN DWORD dwDesiredAccess,
+ IN BOOL bInheritHandle,
+ IN LPCWSTR lpName)
+{
+ HANDLE hMutex = NULL;
+ PAL_ERROR palError = NO_ERROR;
+ CPalThread *pthr = NULL;
+ char utf8Name[SHARED_MEMORY_MAX_NAME_CHAR_COUNT + 1];
+
+ PERF_ENTRY(OpenMutexW);
+ ENTRY("OpenMutexW(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%S))\n",
+ dwDesiredAccess, bInheritHandle, lpName, lpName?lpName:W16_NULLSTRING);
+
+ pthr = InternalGetCurrentThread();
+
+ /* validate parameters */
+ if (lpName == nullptr)
+ {
+ ERROR("name is NULL\n");
+ palError = ERROR_INVALID_PARAMETER;
+ goto OpenMutexWExit;
+ }
+
+ {
+ int bytesWritten = WideCharToMultiByte(CP_ACP, 0, lpName, -1, utf8Name, _countof(utf8Name), nullptr, nullptr);
+ if (bytesWritten == 0)
+ {
+ DWORD errorCode = GetLastError();
+ if (errorCode == ERROR_INSUFFICIENT_BUFFER)
+ {
+ palError = static_cast<DWORD>(SharedMemoryError::NameTooLong);
+ }
+ else
+ {
+ ASSERT("WideCharToMultiByte failed (%u)\n", errorCode);
+ palError = errorCode;
+ }
+ goto OpenMutexWExit;
+ }
+ }
+
+ palError = InternalOpenMutex(pthr, dwDesiredAccess, bInheritHandle, lpName == nullptr ? nullptr : utf8Name, &hMutex);
+
+OpenMutexWExit:
+ if (NO_ERROR != palError)
+ {
+ pthr->SetLastError(palError);
+ }
+
+ LOGEXIT("OpenMutexW returns HANDLE %p\n", hMutex);
+ PERF_EXIT(OpenMutexW);
+
+ return hMutex;
+}
+
+/*++
+Function:
+ InternalOpenMutex
+
+Note:
+ dwDesiredAccess is currently ignored (no Win32 object security support)
+ bInheritHandle is currently ignored (handles to mutexes are not inheritable)
+
+Parameters:
+ pthr -- thread data for calling thread
+ phEvent -- on success, receives the allocated mutex handle
+
+ See MSDN docs on OpenMutex for all other parameters.
+--*/
+
+PAL_ERROR
+CorUnix::InternalOpenMutex(
+ CPalThread *pthr,
+ DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ LPCSTR lpName,
+ HANDLE *phMutex
+ )
+{
+ CObjectAttributes oa;
+ PAL_ERROR palError = NO_ERROR;
+ IPalObject *pobjMutex = NULL;
+ IPalObject *pobjRegisteredMutex = NULL;
+ HANDLE hMutex = nullptr;
+
+ _ASSERTE(NULL != pthr);
+ _ASSERTE(NULL != lpName);
+ _ASSERTE(NULL != phMutex);
+
+ ENTRY("InternalOpenMutex(pthr=%p, dwDesiredAcces=%d, bInheritHandle=%d, "
+ "lpName=%p, phMutex=%p)\n",
+ pthr,
+ dwDesiredAccess,
+ bInheritHandle,
+ lpName,
+ phMutex
+ );
+
+ palError = g_pObjectManager->AllocateObject(
+ pthr,
+ &otNamedMutex,
+ &oa,
+ &pobjMutex
+ );
+
+ if (NO_ERROR != palError)
+ {
+ goto InternalOpenMutexExit;
+ }
+
+ palError = g_pObjectManager->RegisterObject(
+ pthr,
+ pobjMutex,
+ &aotNamedMutex,
+ dwDesiredAccess,
+ &hMutex,
+ &pobjRegisteredMutex
+ );
+
+ if (palError != NO_ERROR)
+ {
+ _ASSERTE(palError != ERROR_ALREADY_EXISTS); // PAL's naming infrastructure is not used for named mutexes
+ _ASSERTE(pobjRegisteredMutex == nullptr);
+ _ASSERTE(hMutex == nullptr);
+ goto InternalOpenMutexExit;
+ }
+
+ // Now that the object has been registered successfully, it would have a reference associated with the handle, so release
+ // the initial reference. Any errors from now on need to revoke the handle.
+ _ASSERTE(pobjRegisteredMutex == pobjMutex);
+ _ASSERTE(hMutex != nullptr);
+ pobjMutex->ReleaseReference(pthr);
+ pobjRegisteredMutex = nullptr;
+
+ {
+ SharedMemoryProcessDataHeader *processDataHeader;
+ try
+ {
+ processDataHeader = NamedMutexProcessData::Open(lpName);
+ }
+ catch (SharedMemoryException ex)
+ {
+ palError = ex.GetErrorCode();
+ goto InternalOpenMutexExit;
+ }
+ if (processDataHeader == nullptr)
+ {
+ palError = ERROR_FILE_NOT_FOUND;
+ goto InternalOpenMutexExit;
+ }
+ SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(pobjMutex, processDataHeader);
+ }
+
+ *phMutex = hMutex;
+ hMutex = nullptr;
+ pobjMutex = nullptr;
+
+InternalOpenMutexExit:
+
+ _ASSERTE(pobjRegisteredMutex == nullptr);
+ if (hMutex != nullptr)
+ {
+ g_pObjectManager->RevokeHandle(pthr, hMutex);
+ }
+ else if (NULL != pobjMutex)
+ {
+ pobjMutex->ReleaseReference(pthr);
+ }
+
+ LOGEXIT("InternalCreateMutex returns %i\n", palError);
+
+ return palError;
+}
+
+
+/* Basic spinlock implementation */
+void SPINLOCKAcquire (LONG * lock, unsigned int flags)
+{
+ size_t loop_seed = 1, loop_count = 0;
+
+ if (flags & SYNCSPINLOCK_F_ASYMMETRIC)
+ {
+ loop_seed = ((size_t)pthread_self() % 10) + 1;
+ }
+ while (InterlockedCompareExchange(lock, 1, 0))
+ {
+ if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed))
+ {
+#if PAL_IGNORE_NORMAL_THREAD_PRIORITY
+ struct timespec tsSleepTime;
+ tsSleepTime.tv_sec = 0;
+ tsSleepTime.tv_nsec = 1;
+ nanosleep(&tsSleepTime, NULL);
+#else
+ sched_yield();
+#endif
+ }
+ }
+
+}
+
+void SPINLOCKRelease (LONG * lock)
+{
+ *lock = 0;
+}
+
+DWORD SPINLOCKTryAcquire (LONG * lock)
+{
+ return InterlockedCompareExchange(lock, 1, 0);
+ // only returns 0 or 1.
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// MutexHelpers
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+void MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(pthread_mutex_t *mutex)
+{
+ _ASSERTE(mutex != nullptr);
+
+ struct AutoCleanup
+ {
+ pthread_mutexattr_t *m_mutexAttributes;
+
+ AutoCleanup() : m_mutexAttributes(nullptr)
+ {
+ }
+
+ ~AutoCleanup()
+ {
+ if (m_mutexAttributes != nullptr)
+ {
+ int error = pthread_mutexattr_destroy(m_mutexAttributes);
+ _ASSERTE(error == 0);
+ }
+ }
+ } autoCleanup;
+
+ pthread_mutexattr_t mutexAttributes;
+ int error = pthread_mutexattr_init(&mutexAttributes);
+ if (error != 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+ }
+ autoCleanup.m_mutexAttributes = &mutexAttributes;
+
+ error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED);
+ _ASSERTE(error == 0);
+
+ error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST);
+ _ASSERTE(error == 0);
+
+ error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE);
+ _ASSERTE(error == 0);
+
+ error = pthread_mutex_init(mutex, &mutexAttributes);
+ if (error != 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(error == EPERM ? SharedMemoryError::IO : SharedMemoryError::OutOfMemory));
+ }
+}
+
+void MutexHelpers::DestroyMutex(pthread_mutex_t *mutex)
+{
+ _ASSERTE(mutex != nullptr);
+
+ int error = pthread_mutex_destroy(mutex);
+ _ASSERTE(error == 0 || error == EBUSY); // the error will be EBUSY if the mutex is locked
+}
+
+MutexTryAcquireLockResult MutexHelpers::TryAcquireLock(pthread_mutex_t *mutex, DWORD timeoutMilliseconds)
+{
+ _ASSERTE(mutex != nullptr);
+
+ int lockResult;
+ switch (timeoutMilliseconds)
+ {
+ case static_cast<DWORD>(-1):
+ lockResult = pthread_mutex_lock(mutex);
+ break;
+
+ case 0:
+ lockResult = pthread_mutex_trylock(mutex);
+ break;
+
+ default:
+ {
+ struct timespec timeoutTime;
+ PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime);
+ _ASSERTE(palError == NO_ERROR);
+ lockResult = pthread_mutex_timedlock(mutex, &timeoutTime);
+ break;
+ }
+ }
+
+ switch (lockResult)
+ {
+ case 0:
+ return MutexTryAcquireLockResult::AcquiredLock;
+
+ case EBUSY:
+ _ASSERTE(timeoutMilliseconds == 0);
+ return MutexTryAcquireLockResult::TimedOut;
+
+ case ETIMEDOUT:
+ _ASSERTE(timeoutMilliseconds != static_cast<DWORD>(-1));
+ _ASSERTE(timeoutMilliseconds != 0);
+ return MutexTryAcquireLockResult::TimedOut;
+
+ case EOWNERDEAD:
+ {
+ int setConsistentResult = pthread_mutex_consistent(mutex);
+ _ASSERTE(setConsistentResult == 0);
+ return MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned;
+ }
+
+ case EAGAIN:
+ throw SharedMemoryException(static_cast<DWORD>(NamedMutexError::MaximumRecursiveLocksReached));
+
+ default:
+ throw SharedMemoryException(static_cast<DWORD>(NamedMutexError::Unknown));
+ }
+}
+
+void MutexHelpers::ReleaseLock(pthread_mutex_t *mutex)
+{
+ _ASSERTE(mutex != nullptr);
+
+ int unlockResult = pthread_mutex_unlock(mutex);
+ _ASSERTE(unlockResult == 0);
+}
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// NamedMutexSharedData
+
+NamedMutexSharedData::NamedMutexSharedData()
+ :
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_timedWaiterCount(0),
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_lockOwnerProcessId(SharedMemoryHelpers::InvalidProcessId),
+ m_lockOwnerThreadId(SharedMemoryHelpers::InvalidSharedThreadId),
+ m_isAbandoned(false)
+{
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ static_assert_no_msg(sizeof(m_timedWaiterCount) == sizeof(LONG)); // for interlocked operations
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired());
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+ MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(&m_lock);
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+}
+
+NamedMutexSharedData::~NamedMutexSharedData()
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired());
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+ MutexHelpers::DestroyMutex(&m_lock);
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+}
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+pthread_mutex_t *NamedMutexSharedData::GetLock()
+{
+ return &m_lock;
+}
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+bool NamedMutexSharedData::HasAnyTimedWaiters() const
+{
+ return
+ InterlockedCompareExchange(
+ const_cast<LONG *>(reinterpret_cast<const LONG *>(&m_timedWaiterCount)),
+ -1 /* Exchange */,
+ -1 /* Comparand */) != 0;
+}
+
+void NamedMutexSharedData::IncTimedWaiterCount()
+{
+ ULONG newValue = InterlockedIncrement(reinterpret_cast<LONG *>(&m_timedWaiterCount));
+ if (newValue == 0)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::OutOfMemory));
+ }
+}
+
+void NamedMutexSharedData::DecTimedWaiterCount()
+{
+ ULONG newValue = InterlockedDecrement(reinterpret_cast<LONG *>(&m_timedWaiterCount));
+ _ASSERTE(newValue + 1 != 0);
+}
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+bool NamedMutexSharedData::IsAbandoned() const
+{
+ _ASSERTE(IsLockOwnedByCurrentThread());
+ return m_isAbandoned;
+}
+
+void NamedMutexSharedData::SetIsAbandoned(bool isAbandoned)
+{
+ _ASSERTE(IsLockOwnedByCurrentThread());
+ _ASSERTE(m_isAbandoned != isAbandoned);
+
+ m_isAbandoned = isAbandoned;
+}
+
+bool NamedMutexSharedData::IsLockOwnedByAnyThread() const
+{
+ return
+ m_lockOwnerProcessId != SharedMemoryHelpers::InvalidProcessId ||
+ m_lockOwnerThreadId != SharedMemoryHelpers::InvalidSharedThreadId;
+}
+
+bool NamedMutexSharedData::IsLockOwnedByCurrentThread() const
+{
+ return m_lockOwnerProcessId == GetCurrentProcessId() && m_lockOwnerThreadId == THREADSilentGetCurrentThreadId();
+}
+
+void NamedMutexSharedData::SetLockOwnerToCurrentThread()
+{
+ m_lockOwnerProcessId = GetCurrentProcessId();
+ _ASSERTE(m_lockOwnerProcessId != SharedMemoryHelpers::InvalidProcessId);
+ m_lockOwnerThreadId = THREADSilentGetCurrentThreadId();
+ _ASSERTE(m_lockOwnerThreadId != SharedMemoryHelpers::InvalidSharedThreadId);
+}
+
+void NamedMutexSharedData::ClearLockOwner()
+{
+ _ASSERTE(IsLockOwnedByCurrentThread());
+
+ m_lockOwnerProcessId = SharedMemoryHelpers::InvalidProcessId;
+ m_lockOwnerThreadId = SharedMemoryHelpers::InvalidSharedThreadId;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// NamedMutexProcessData
+
+// This value should only be incremented if a non-backward-compatible change to the sync system is made. A process would fail to
+// open a mutex created with a different sync system version.
+const UINT8 NamedMutexProcessData::SyncSystemVersion = 1;
+
+const DWORD NamedMutexProcessData::PollLoopMaximumSleepMilliseconds = 100;
+
+SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen(LPCSTR name, bool acquireLockIfCreated, bool *createdRef)
+{
+ return CreateOrOpen(name, true /* createIfNotExist */, acquireLockIfCreated, createdRef);
+}
+
+SharedMemoryProcessDataHeader *NamedMutexProcessData::Open(LPCSTR name)
+{
+ return CreateOrOpen(name, false /* createIfNotExist */, false /* acquireLockIfCreated */, nullptr /* createdRef */);
+}
+
+SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen(
+ LPCSTR name,
+ bool createIfNotExist,
+ bool acquireLockIfCreated,
+ bool *createdRef)
+{
+ _ASSERTE(name != nullptr);
+ _ASSERTE(createIfNotExist || !acquireLockIfCreated);
+
+ struct AutoCleanup
+ {
+ bool m_acquiredCreationDeletionProcessLock;
+ bool m_acquiredCreationDeletionFileLock;
+ SharedMemoryProcessDataHeader *m_processDataHeader;
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ char *m_lockFilePath;
+ SIZE_T m_sessionDirectoryPathCharCount;
+ bool m_createdLockFile;
+ int m_lockFileDescriptor;
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ bool m_cancel;
+
+ AutoCleanup()
+ : m_acquiredCreationDeletionProcessLock(false),
+ m_acquiredCreationDeletionFileLock(false),
+ m_processDataHeader(nullptr),
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_lockFilePath(nullptr),
+ m_sessionDirectoryPathCharCount(0),
+ m_createdLockFile(false),
+ m_lockFileDescriptor(-1),
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_cancel(false)
+ {
+ }
+
+ ~AutoCleanup()
+ {
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ if (!m_cancel)
+ {
+ if (m_lockFileDescriptor != -1)
+ {
+ SharedMemoryHelpers::CloseFile(m_lockFileDescriptor);
+ }
+
+ if (m_createdLockFile)
+ {
+ _ASSERTE(m_lockFilePath != nullptr);
+ unlink(m_lockFilePath);
+ }
+
+ if (m_sessionDirectoryPathCharCount != 0)
+ {
+ _ASSERTE(m_lockFilePath != nullptr);
+ m_lockFilePath[m_sessionDirectoryPathCharCount] = '\0';
+ rmdir(m_lockFilePath);
+ }
+ }
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+ if (m_acquiredCreationDeletionFileLock)
+ {
+ SharedMemoryManager::ReleaseCreationDeletionFileLock();
+ }
+
+ if (!m_cancel && m_processDataHeader != nullptr)
+ {
+ _ASSERTE(m_acquiredCreationDeletionProcessLock);
+ m_processDataHeader->DecRefCount();
+ }
+
+ if (m_acquiredCreationDeletionProcessLock)
+ {
+ SharedMemoryManager::ReleaseCreationDeletionProcessLock();
+ }
+ }
+ } autoCleanup;
+
+ SharedMemoryManager::AcquireCreationDeletionProcessLock();
+ autoCleanup.m_acquiredCreationDeletionProcessLock = true;
+
+ // Create or open the shared memory
+ bool created;
+ SharedMemoryProcessDataHeader *processDataHeader =
+ SharedMemoryProcessDataHeader::CreateOrOpen(
+ name,
+ SharedMemorySharedDataHeader(SharedMemoryType::Mutex, SyncSystemVersion),
+ sizeof(NamedMutexSharedData),
+ createIfNotExist,
+ &created);
+ if (createdRef != nullptr)
+ {
+ *createdRef = created;
+ }
+ if (created)
+ {
+ // If the shared memory file was created, the creation/deletion file lock would have been acquired so that we can
+ // initialize the shared data
+ autoCleanup.m_acquiredCreationDeletionFileLock = true;
+ }
+ if (processDataHeader == nullptr)
+ {
+ _ASSERTE(!createIfNotExist);
+ return nullptr;
+ }
+ autoCleanup.m_processDataHeader = processDataHeader;
+
+ if (created)
+ {
+ // Initialize the shared data
+ new(processDataHeader->GetSharedDataHeader()->GetData()) NamedMutexSharedData;
+ }
+
+ if (processDataHeader->GetData() == nullptr)
+ {
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ // Create the lock files directory
+ char lockFilePath[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1];
+ SIZE_T lockFilePathCharCount =
+ SharedMemoryHelpers::CopyString(lockFilePath, 0, SHARED_MEMORY_LOCK_FILES_DIRECTORY_PATH);
+ if (created)
+ {
+ SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */);
+ }
+
+ // Create the session directory
+ lockFilePath[lockFilePathCharCount++] = '/';
+ SharedMemoryId *id = processDataHeader->GetId();
+ lockFilePathCharCount = id->AppendSessionDirectoryName(lockFilePath, lockFilePathCharCount);
+ if (created)
+ {
+ SharedMemoryHelpers::EnsureDirectoryExists(lockFilePath, true /* isGlobalLockAcquired */);
+ autoCleanup.m_lockFilePath = lockFilePath;
+ autoCleanup.m_sessionDirectoryPathCharCount = lockFilePathCharCount;
+ }
+
+ // Create or open the lock file
+ lockFilePath[lockFilePathCharCount++] = '/';
+ lockFilePathCharCount =
+ SharedMemoryHelpers::CopyString(lockFilePath, lockFilePathCharCount, id->GetName(), id->GetNameCharCount());
+ int lockFileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(lockFilePath, created);
+ if (lockFileDescriptor == -1)
+ {
+ _ASSERTE(!created);
+ if (createIfNotExist)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(SharedMemoryError::IO));
+ }
+ return nullptr;
+ }
+ autoCleanup.m_createdLockFile = created;
+ autoCleanup.m_lockFileDescriptor = lockFileDescriptor;
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+ // Create the process data
+ void *processDataBuffer = SharedMemoryHelpers::Alloc(sizeof(NamedMutexProcessData));
+ AutoFreeBuffer autoFreeProcessDataBuffer(processDataBuffer);
+ NamedMutexProcessData *processData =
+ new(processDataBuffer)
+ NamedMutexProcessData(
+ processDataHeader
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ ,
+ lockFileDescriptor
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ );
+ autoFreeProcessDataBuffer.Cancel();
+ processDataHeader->SetData(processData);
+
+ // If the mutex was created and if requested, acquire the lock initially while holding the creation/deletion locks
+ if (created && acquireLockIfCreated)
+ {
+ MutexTryAcquireLockResult tryAcquireLockResult = processData->TryAcquireLock(0);
+ _ASSERTE(tryAcquireLockResult == MutexTryAcquireLockResult::AcquiredLock);
+ }
+ }
+
+ autoCleanup.m_cancel = true;
+ return processDataHeader;
+}
+
+NamedMutexProcessData::NamedMutexProcessData(
+ SharedMemoryProcessDataHeader *processDataHeader
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ ,
+ int sharedLockFileDescriptor
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+)
+ :
+ m_processDataHeader(processDataHeader),
+ m_lockCount(0),
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_sharedLockFileDescriptor(sharedLockFileDescriptor),
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ m_lockOwnerThread(nullptr),
+ m_nextInThreadOwnedNamedMutexList(nullptr)
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(processDataHeader != nullptr);
+
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ _ASSERTE(sharedLockFileDescriptor != -1);
+
+ m_processLockHandle = CreateMutex(nullptr /* lpMutexAttributes */, false /* bInitialOwner */, nullptr /* lpName */);
+ if (m_processLockHandle == nullptr)
+ {
+ throw SharedMemoryException(GetLastError());
+ }
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+}
+
+void NamedMutexProcessData::Close(bool isAbruptShutdown, bool releaseSharedData)
+{
+ _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
+ _ASSERTE(!releaseSharedData || SharedMemoryManager::IsCreationDeletionFileLockAcquired());
+
+ // If the process is shutting down abruptly without having closed some mutexes, there could still be threads running with
+ // active references to the mutex. So when shutting down abruptly, don't clean up any object or global process-local state.
+ if (!isAbruptShutdown)
+ {
+ CPalThread *lockOwnerThread = m_lockOwnerThread;
+ if (lockOwnerThread != nullptr)
+ {
+ // The mutex was not released before it was closed. If the lock is owned by the current thread, abandon the mutex.
+ // In both cases, clean up the owner thread's list of owned mutexes.
+ lockOwnerThread->synchronizationInfo.RemoveOwnedNamedMutex(this);
+ if (lockOwnerThread == GetCurrentPalThread())
+ {
+ Abandon();
+ }
+ else
+ {
+ m_lockOwnerThread = nullptr;
+ }
+ }
+
+ if (releaseSharedData)
+ {
+ GetSharedData()->~NamedMutexSharedData();
+ }
+ }
+
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ if (!isAbruptShutdown)
+ {
+ CloseHandle(m_processLockHandle);
+ SharedMemoryHelpers::CloseFile(m_sharedLockFileDescriptor);
+ }
+
+ if (!releaseSharedData)
+ {
+ return;
+ }
+
+ // Delete the lock file, and the session directory if it's not empty
+ char path[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1];
+ SIZE_T sessionDirectoryPathCharCount = SharedMemoryHelpers::CopyString(path, 0, SHARED_MEMORY_LOCK_FILES_DIRECTORY_PATH);
+ path[sessionDirectoryPathCharCount++] = '/';
+ SharedMemoryId *id = m_processDataHeader->GetId();
+ sessionDirectoryPathCharCount = id->AppendSessionDirectoryName(path, sessionDirectoryPathCharCount);
+ path[sessionDirectoryPathCharCount++] = '/';
+ SharedMemoryHelpers::CopyString(path, sessionDirectoryPathCharCount, id->GetName(), id->GetNameCharCount());
+ unlink(path);
+ path[sessionDirectoryPathCharCount] = '\0';
+ rmdir(path);
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+}
+
+NamedMutexSharedData *NamedMutexProcessData::GetSharedData() const
+{
+ return reinterpret_cast<NamedMutexSharedData *>(m_processDataHeader->GetSharedDataHeader()->GetData());
+}
+
+void NamedMutexProcessData::SetLockOwnerThread(CorUnix::CPalThread *lockOwnerThread)
+{
+ _ASSERTE(lockOwnerThread == nullptr || lockOwnerThread == GetCurrentPalThread());
+ _ASSERTE(GetSharedData()->IsLockOwnedByCurrentThread());
+
+ m_lockOwnerThread = lockOwnerThread;
+}
+
+NamedMutexProcessData *NamedMutexProcessData::GetNextInThreadOwnedNamedMutexList() const
+{
+ return m_nextInThreadOwnedNamedMutexList;
+}
+
+void NamedMutexProcessData::SetNextInThreadOwnedNamedMutexList(NamedMutexProcessData *next)
+{
+ m_nextInThreadOwnedNamedMutexList = next;
+}
+
+MutexTryAcquireLockResult NamedMutexProcessData::TryAcquireLock(DWORD timeoutMilliseconds)
+{
+ NamedMutexSharedData *sharedData = GetSharedData();
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+ MutexTryAcquireLockResult result = MutexHelpers::TryAcquireLock(sharedData->GetLock(), timeoutMilliseconds);
+ if (result == MutexTryAcquireLockResult::TimedOut)
+ {
+ return result;
+ }
+
+ // Check if a recursive lock was just taken. The recursion level is tracked manually so that the lock owner can be cleared
+ // at the appropriate time, see ReleaseLock().
+ if (m_lockCount != 0)
+ {
+ _ASSERTE(sharedData->IsLockOwnedByCurrentThread()); // otherwise, this thread would not have acquired the lock
+ _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this));
+
+ if (m_lockCount + 1 < m_lockCount)
+ {
+ MutexHelpers::ReleaseLock(sharedData->GetLock());
+ throw SharedMemoryException(static_cast<DWORD>(NamedMutexError::MaximumRecursiveLocksReached));
+ }
+ ++m_lockCount;
+
+ // The lock is released upon acquiring a recursive lock from the thread that already owns the lock
+ MutexHelpers::ReleaseLock(sharedData->GetLock());
+
+ _ASSERTE(result != MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned);
+ _ASSERTE(!sharedData->IsAbandoned());
+ return result;
+ }
+
+ // The non-recursive case is handled below (skip the #else and see below that)
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ // If a timeout is specified, determine the start time
+ DWORD startTime = 0;
+ if (timeoutMilliseconds != static_cast<DWORD>(-1) && timeoutMilliseconds != 0)
+ {
+ startTime = GetTickCount();
+ }
+
+ // Acquire the process lock. A file lock can only be acquired once per file descriptor, so to synchronize the threads of
+ // this process, the process lock is used.
+ while (true)
+ {
+ DWORD waitResult = WaitForSingleObject(m_processLockHandle, timeoutMilliseconds);
+ switch (waitResult)
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED: // abandoned state for the process lock is irrelevant, the shared lock will also have been abandoned
+ break;
+
+ case WAIT_TIMEOUT:
+ return MutexTryAcquireLockResult::TimedOut;
+
+ case WAIT_IO_COMPLETION:
+ continue;
+
+ case WAIT_FAILED:
+ throw SharedMemoryException(GetLastError());
+
+ default:
+ _ASSERTE(false);
+ break;
+ }
+ break;
+ }
+
+ struct AutoReleaseProcessLock
+ {
+ HANDLE m_processLockHandle;
+ bool m_cancel;
+
+ AutoReleaseProcessLock(HANDLE processLockHandle) : m_processLockHandle(processLockHandle), m_cancel(false)
+ {
+ }
+
+ ~AutoReleaseProcessLock()
+ {
+ if (!m_cancel)
+ {
+ ReleaseMutex(m_processLockHandle);
+ }
+ }
+ } autoReleaseProcessLock(m_processLockHandle);
+
+ // Check if it's a recursive lock attempt
+ if (m_lockCount != 0)
+ {
+ _ASSERTE(sharedData->IsLockOwnedByCurrentThread()); // otherwise, this thread would not have acquired the process lock
+ _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this));
+
+ if (m_lockCount + 1 < m_lockCount)
+ {
+ throw SharedMemoryException(static_cast<DWORD>(NamedMutexError::MaximumRecursiveLocksReached));
+ }
+ ++m_lockCount;
+
+ // The process lock is released upon acquiring a recursive lock from the thread that already owns the lock
+ return MutexTryAcquireLockResult::AcquiredLock;
+ }
+
+ switch (timeoutMilliseconds)
+ {
+ case static_cast<DWORD>(-1):
+ {
+ // The file lock API does not have a timeout on the wait, so timed waiters will poll the file lock in a loop,
+ // sleeping for a short duration in-between. Due to the polling nature of a timed wait, timed waiters will almost
+ // never acquire the file lock as long as there are also untimed waiters. So, in order to make the file lock
+ // acquisition reasonable, when there are timed waiters, have untimed waiters also use polling.
+ bool acquiredFileLock = false;
+ while (sharedData->HasAnyTimedWaiters())
+ {
+ if (SharedMemoryHelpers::TryAcquireFileLock(m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB))
+ {
+ acquiredFileLock = true;
+ break;
+ }
+ Sleep(PollLoopMaximumSleepMilliseconds);
+ }
+ if (acquiredFileLock)
+ {
+ break;
+ }
+
+ acquiredFileLock = SharedMemoryHelpers::TryAcquireFileLock(m_sharedLockFileDescriptor, LOCK_EX);
+ _ASSERTE(acquiredFileLock);
+ break;
+ }
+
+ case 0:
+ if (!SharedMemoryHelpers::TryAcquireFileLock(m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB))
+ {
+ return MutexTryAcquireLockResult::TimedOut;
+ }
+ break;
+
+ default:
+ {
+ // Try to acquire the file lock without waiting
+ if (SharedMemoryHelpers::TryAcquireFileLock(m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB))
+ {
+ break;
+ }
+
+ // The file lock API does not have a timeout on the wait, so timed waiters need to poll the file lock in a loop,
+ // sleeping for a short duration in-between. Due to the polling nature of a timed wait, timed waiters will almost
+ // never acquire the file lock as long as there are also untimed waiters. So, in order to make the file lock
+ // acquisition reasonable, record that there is a timed waiter, to have untimed waiters also use polling.
+ sharedData->IncTimedWaiterCount();
+ struct AutoDecTimedWaiterCount
+ {
+ NamedMutexSharedData *m_sharedData;
+
+ AutoDecTimedWaiterCount(NamedMutexSharedData *sharedData) : m_sharedData(sharedData)
+ {
+ }
+
+ ~AutoDecTimedWaiterCount()
+ {
+ m_sharedData->DecTimedWaiterCount();
+ }
+ } autoDecTimedWaiterCount(sharedData);
+
+ // Poll for the file lock
+ do
+ {
+ DWORD elapsedMilliseconds = GetTickCount() - startTime;
+ if (elapsedMilliseconds >= timeoutMilliseconds)
+ {
+ return MutexTryAcquireLockResult::TimedOut;
+ }
+
+ DWORD remainingMilliseconds = timeoutMilliseconds - elapsedMilliseconds;
+ DWORD sleepMilliseconds =
+ remainingMilliseconds < PollLoopMaximumSleepMilliseconds
+ ? remainingMilliseconds
+ : PollLoopMaximumSleepMilliseconds;
+ Sleep(sleepMilliseconds);
+ } while (!SharedMemoryHelpers::TryAcquireFileLock(m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB));
+ break;
+ }
+ }
+
+ // There cannot be any exceptions after this
+ autoReleaseProcessLock.m_cancel = true;
+
+ // After acquiring the file lock, if we find that a lock owner is already designated, the process that previously owned the
+ // lock must have terminated while holding the lock.
+ MutexTryAcquireLockResult result =
+ sharedData->IsLockOwnedByAnyThread()
+ ? MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned
+ : MutexTryAcquireLockResult::AcquiredLock;
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+
+ sharedData->SetLockOwnerToCurrentThread();
+ m_lockCount = 1;
+ CPalThread *currentThread = GetCurrentPalThread();
+ SetLockOwnerThread(currentThread);
+ currentThread->synchronizationInfo.AddOwnedNamedMutex(this);
+
+ if (sharedData->IsAbandoned())
+ {
+ // The thread that previously owned the lock did not release it before exiting
+ sharedData->SetIsAbandoned(false);
+ result = MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned;
+ }
+ return result;
+}
+
+void NamedMutexProcessData::ReleaseLock()
+{
+ if (!GetSharedData()->IsLockOwnedByCurrentThread())
+ {
+ throw SharedMemoryException(static_cast<DWORD>(NamedMutexError::ThreadHasNotAcquiredMutex));
+ }
+
+ _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this));
+
+ _ASSERTE(m_lockCount != 0);
+ --m_lockCount;
+ if (m_lockCount != 0)
+ {
+ return;
+ }
+
+ GetCurrentPalThread()->synchronizationInfo.RemoveOwnedNamedMutex(this);
+ SetLockOwnerThread(nullptr);
+ ActuallyReleaseLock();
+}
+
+void NamedMutexProcessData::Abandon()
+{
+ NamedMutexSharedData *sharedData = GetSharedData();
+ _ASSERTE(sharedData->IsLockOwnedByCurrentThread());
+ _ASSERTE(m_lockCount != 0);
+
+ sharedData->SetIsAbandoned(true);
+ m_lockCount = 0;
+ SetLockOwnerThread(nullptr);
+ ActuallyReleaseLock();
+}
+
+void NamedMutexProcessData::ActuallyReleaseLock()
+{
+ NamedMutexSharedData *sharedData = GetSharedData();
+ _ASSERTE(sharedData->IsLockOwnedByCurrentThread());
+ _ASSERTE(!GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this));
+ _ASSERTE(m_lockCount == 0);
+
+ sharedData->ClearLockOwner();
+
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
+ MutexHelpers::ReleaseLock(sharedData->GetLock());
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
+ SharedMemoryHelpers::ReleaseFileLock(m_sharedLockFileDescriptor);
+ ReleaseMutex(m_processLockHandle);
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
+}