}" HAVE_FULLY_FEATURED_PTHREAD_MUTEXES)
set(CMAKE_REQUIRED_LIBRARIES)
+if(NOT CLR_CMAKE_PLATFORM_ARCH_ARM AND NOT CLR_CMAKE_PLATFORM_ARCH_ARM64)
+ set(CMAKE_REQUIRED_LIBRARIES pthread)
+ check_cxx_source_runs("
+ // This test case verifies the pthread process-shared robust mutex's cross-process abandon detection. The parent process starts
+ // a child process that locks the mutex, the process process then waits to acquire the lock, and the child process abandons the
+ // mutex by exiting the process while holding the lock. The parent process should then be released from its wait, be assigned
+ // ownership of the lock, and be notified that the mutex was abandoned.
+
+ #include <sys/mman.h>
+ #include <sys/time.h>
+
+ #include <errno.h>
+ #include <pthread.h>
+ #include <stdio.h>
+ #include <unistd.h>
+
+ #include <new>
+ using namespace std;
+
+ struct Shm
+ {
+ pthread_mutex_t syncMutex;
+ pthread_cond_t syncCondition;
+ pthread_mutex_t robustMutex;
+ int conditionValue;
+
+ Shm() : conditionValue(0)
+ {
+ }
+ } *shm;
+
+ int GetFailTimeoutTime(struct timespec *timeoutTimeRef)
+ {
+ int getTimeResult = clock_gettime(CLOCK_REALTIME, timeoutTimeRef);
+ if (getTimeResult != 0)
+ {
+ struct timeval tv;
+ getTimeResult = gettimeofday(&tv, NULL);
+ if (getTimeResult != 0)
+ return 1;
+ timeoutTimeRef->tv_sec = tv.tv_sec;
+ timeoutTimeRef->tv_nsec = tv.tv_usec * 1000;
+ }
+ timeoutTimeRef->tv_sec += 30;
+ return 0;
+ }
+
+ int WaitForConditionValue(int desiredConditionValue)
+ {
+ struct timespec timeoutTime;
+ if (GetFailTimeoutTime(&timeoutTime) != 0)
+ return 1;
+ if (pthread_mutex_timedlock(&shm->syncMutex, &timeoutTime) != 0)
+ return 1;
+
+ if (shm->conditionValue != desiredConditionValue)
+ {
+ if (GetFailTimeoutTime(&timeoutTime) != 0)
+ return 1;
+ if (pthread_cond_timedwait(&shm->syncCondition, &shm->syncMutex, &timeoutTime) != 0)
+ return 1;
+ if (shm->conditionValue != desiredConditionValue)
+ return 1;
+ }
+
+ if (pthread_mutex_unlock(&shm->syncMutex) != 0)
+ return 1;
+ return 0;
+ }
+
+ int SetConditionValue(int newConditionValue)
+ {
+ struct timespec timeoutTime;
+ if (GetFailTimeoutTime(&timeoutTime) != 0)
+ return 1;
+ if (pthread_mutex_timedlock(&shm->syncMutex, &timeoutTime) != 0)
+ return 1;
+
+ shm->conditionValue = newConditionValue;
+ if (pthread_cond_signal(&shm->syncCondition) != 0)
+ return 1;
+
+ if (pthread_mutex_unlock(&shm->syncMutex) != 0)
+ return 1;
+ return 0;
+ }
+
+ void DoTest_Child();
+
+ int DoTest()
+ {
+ // Map some shared memory
+ void *shmBuffer = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+ if (shmBuffer == MAP_FAILED)
+ return 1;
+ shm = new(shmBuffer) Shm;
+
+ // Create sync mutex
+ pthread_mutexattr_t syncMutexAttributes;
+ if (pthread_mutexattr_init(&syncMutexAttributes) != 0)
+ return 1;
+ if (pthread_mutexattr_setpshared(&syncMutexAttributes, PTHREAD_PROCESS_SHARED) != 0)
+ return 1;
+ if (pthread_mutex_init(&shm->syncMutex, &syncMutexAttributes) != 0)
+ return 1;
+ if (pthread_mutexattr_destroy(&syncMutexAttributes) != 0)
+ return 1;
+
+ // Create sync condition
+ pthread_condattr_t syncConditionAttributes;
+ if (pthread_condattr_init(&syncConditionAttributes) != 0)
+ return 1;
+ if (pthread_condattr_setpshared(&syncConditionAttributes, PTHREAD_PROCESS_SHARED) != 0)
+ return 1;
+ if (pthread_cond_init(&shm->syncCondition, &syncConditionAttributes) != 0)
+ return 1;
+ if (pthread_condattr_destroy(&syncConditionAttributes) != 0)
+ return 1;
+
+ // Create the robust mutex that will be tested
+ pthread_mutexattr_t robustMutexAttributes;
+ if (pthread_mutexattr_init(&robustMutexAttributes) != 0)
+ return 1;
+ if (pthread_mutexattr_setpshared(&robustMutexAttributes, PTHREAD_PROCESS_SHARED) != 0)
+ return 1;
+ if (pthread_mutexattr_setrobust(&robustMutexAttributes, PTHREAD_MUTEX_ROBUST) != 0)
+ return 1;
+ if (pthread_mutex_init(&shm->robustMutex, &robustMutexAttributes) != 0)
+ return 1;
+ if (pthread_mutexattr_destroy(&robustMutexAttributes) != 0)
+ return 1;
+
+ // Start child test process
+ int error = fork();
+ if (error == -1)
+ return 1;
+ if (error == 0)
+ {
+ DoTest_Child();
+ return -1;
+ }
+
+ // Wait for child to take a lock
+ WaitForConditionValue(1);
+
+ // Wait to try to take a lock. Meanwhile, child abandons the robust mutex.
+ struct timespec timeoutTime;
+ if (GetFailTimeoutTime(&timeoutTime) != 0)
+ return 1;
+ error = pthread_mutex_timedlock(&shm->robustMutex, &timeoutTime);
+ if (error != EOWNERDEAD) // expect to be notified that the robust mutex was abandoned
+ return 1;
+ if (pthread_mutex_consistent(&shm->robustMutex) != 0)
+ return 1;
+
+ if (pthread_mutex_unlock(&shm->robustMutex) != 0)
+ return 1;
+ if (pthread_mutex_destroy(&shm->robustMutex) != 0)
+ return 1;
+ return 0;
+ }
+
+ void DoTest_Child()
+ {
+ // Lock the robust mutex
+ struct timespec timeoutTime;
+ if (GetFailTimeoutTime(&timeoutTime) != 0)
+ return;
+ if (pthread_mutex_timedlock(&shm->robustMutex, &timeoutTime) != 0)
+ return;
+
+ // Notify parent that robust mutex is locked
+ if (SetConditionValue(1) != 0)
+ return;
+
+ // Wait a short period to let the parent block on waiting for a lock
+ sleep(1);
+
+ // Abandon the mutex by exiting the process while holding the lock. Parent's wait should be released by EOWNERDEAD.
+ }
+
+ int main()
+ {
+ int result = DoTest();
+ return result >= 0 ? result : 0;
+ }" HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES)
+ set(CMAKE_REQUIRED_LIBRARIES)
+endif()
+
if(CMAKE_SYSTEM_NAME STREQUAL Darwin)
if(NOT HAVE_LIBUUID_H)
unset(HAVE_LIBUUID_H CACHE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Named mutex
+// Temporarily disabling usage of pthread process-shared mutexes on ARM/ARM64 due to functional issues that cannot easily be
+// detected with code due to hangs. See https://github.com/dotnet/coreclr/issues/5456.
+#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES && HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES && !(defined(_ARM_) || defined(_ARM64_))
+ #define NAMED_MUTEX_USE_PTHREAD_MUTEX 1
+#else
+ #define NAMED_MUTEX_USE_PTHREAD_MUTEX 0
+#endif
+
enum class NamedMutexError : DWORD
{
MaximumRecursiveLocksReached = ERROR_NOT_ENOUGH_MEMORY,
TimedOut
};
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
class MutexHelpers
{
public:
static MutexTryAcquireLockResult TryAcquireLock(pthread_mutex_t *mutex, DWORD timeoutMilliseconds);
static void ReleaseLock(pthread_mutex_t *mutex);
};
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
class NamedMutexSharedData
{
private:
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
pthread_mutex_t m_lock;
-#else // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
UINT32 m_timedWaiterCount;
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
UINT32 m_lockOwnerProcessId;
UINT64 m_lockOwnerThreadId;
bool m_isAbandoned;
NamedMutexSharedData();
~NamedMutexSharedData();
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
public:
pthread_mutex_t *GetLock();
-#else // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
public:
bool HasAnyTimedWaiters() const;
void IncTimedWaiterCount();
void DecTimedWaiterCount();
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
public:
bool IsAbandoned() const;
SharedMemoryProcessDataHeader *m_processDataHeader;
NamedMutexSharedData *m_sharedData;
SIZE_T m_lockCount;
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
HANDLE m_processLockHandle;
int m_sharedLockFileDescriptor;
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
CorUnix::CPalThread *m_lockOwnerThread;
NamedMutexProcessData *m_nextInThreadOwnedNamedMutexList;
public:
NamedMutexProcessData(
SharedMemoryProcessDataHeader *processDataHeader
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
,
int sharedLockFileDescriptor
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
);
virtual void Close(bool isAbruptShutdown, bool releaseSharedData) override;
#include "../synchmgr/synchmanager.hpp"
+#include <sys/file.h>
#include <sys/types.h>
#include <errno.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MutexHelpers
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
void MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(pthread_mutex_t *mutex)
{
_ASSERTE(mutex != nullptr);
int unlockResult = pthread_mutex_unlock(mutex);
_ASSERTE(unlockResult == 0);
}
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NamedMutexSharedData
NamedMutexSharedData::NamedMutexSharedData()
:
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_timedWaiterCount(0),
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_lockOwnerProcessId(SharedMemoryHelpers::InvalidProcessId),
m_lockOwnerThreadId(SharedMemoryHelpers::InvalidSharedThreadId),
m_isAbandoned(false)
{
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
static_assert_no_msg(sizeof(m_timedWaiterCount) == sizeof(LONG)); // for interlocked operations
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
_ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
_ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired());
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(&m_lock);
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
}
NamedMutexSharedData::~NamedMutexSharedData()
_ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
_ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired());
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
MutexHelpers::DestroyMutex(&m_lock);
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
}
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
pthread_mutex_t *NamedMutexSharedData::GetLock()
{
return &m_lock;
}
-#else // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
bool NamedMutexSharedData::HasAnyTimedWaiters() const
{
return
ULONG newValue = InterlockedDecrement(reinterpret_cast<LONG *>(&m_timedWaiterCount));
_ASSERTE(newValue + 1 != 0);
}
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
bool NamedMutexSharedData::IsAbandoned() const
{
bool m_acquiredCreationDeletionProcessLock;
bool m_acquiredCreationDeletionFileLock;
SharedMemoryProcessDataHeader *m_processDataHeader;
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
char *m_lockFilePath;
SIZE_T m_sessionDirectoryPathCharCount;
bool m_createdLockFile;
int m_lockFileDescriptor;
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
bool m_cancel;
AutoCleanup()
: m_acquiredCreationDeletionProcessLock(false),
m_acquiredCreationDeletionFileLock(false),
m_processDataHeader(nullptr),
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_lockFilePath(nullptr),
m_sessionDirectoryPathCharCount(0),
m_createdLockFile(false),
m_lockFileDescriptor(-1),
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_cancel(false)
{
}
~AutoCleanup()
{
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
if (!m_cancel)
{
if (m_lockFileDescriptor != -1)
rmdir(m_lockFilePath);
}
}
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
if (m_acquiredCreationDeletionFileLock)
{
if (processDataHeader->GetData() == nullptr)
{
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
// Create the lock files directory
char lockFilePath[SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1];
SIZE_T lockFilePathCharCount =
}
autoCleanup.m_createdLockFile = created;
autoCleanup.m_lockFileDescriptor = lockFileDescriptor;
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
// Create the process data
void *processDataBuffer = SharedMemoryHelpers::Alloc(sizeof(NamedMutexProcessData));
new(processDataBuffer)
NamedMutexProcessData(
processDataHeader
- #if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #if !NAMED_MUTEX_USE_PTHREAD_MUTEX
,
lockFileDescriptor
- #endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+ #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
);
autoFreeProcessDataBuffer.Cancel();
processDataHeader->SetData(processData);
NamedMutexProcessData::NamedMutexProcessData(
SharedMemoryProcessDataHeader *processDataHeader
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
,
int sharedLockFileDescriptor
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
)
:
m_processDataHeader(processDataHeader),
m_lockCount(0),
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_sharedLockFileDescriptor(sharedLockFileDescriptor),
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
m_lockOwnerThread(nullptr),
m_nextInThreadOwnedNamedMutexList(nullptr)
{
_ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired());
_ASSERTE(processDataHeader != nullptr);
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
_ASSERTE(sharedLockFileDescriptor != -1);
m_processLockHandle = CreateMutex(nullptr /* lpMutexAttributes */, false /* bInitialOwner */, nullptr /* lpName */);
{
throw SharedMemoryException(GetLastError());
}
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
}
void NamedMutexProcessData::Close(bool isAbruptShutdown, bool releaseSharedData)
}
}
-#if !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if !NAMED_MUTEX_USE_PTHREAD_MUTEX
if (!isAbruptShutdown)
{
CloseHandle(m_processLockHandle);
unlink(path);
path[sessionDirectoryPathCharCount] = '\0';
rmdir(path);
-#endif // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX
}
NamedMutexSharedData *NamedMutexProcessData::GetSharedData() const
{
NamedMutexSharedData *sharedData = GetSharedData();
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
MutexTryAcquireLockResult result = MutexHelpers::TryAcquireLock(sharedData->GetLock(), timeoutMilliseconds);
if (result == MutexTryAcquireLockResult::TimedOut)
{
}
// The non-recursive case is handled below (skip the #else and see below that)
-#else // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#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)
sharedData->IsLockOwnedByAnyThread()
? MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned
: MutexTryAcquireLockResult::AcquiredLock;
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
sharedData->SetLockOwnerToCurrentThread();
m_lockCount = 1;
sharedData->ClearLockOwner();
-#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#if NAMED_MUTEX_USE_PTHREAD_MUTEX
MutexHelpers::ReleaseLock(sharedData->GetLock());
-#else // !HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX
SharedMemoryHelpers::ReleaseFileLock(m_sharedLockFileDescriptor);
ReleaseMutex(m_processLockHandle);
-#endif // HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
+#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX
}