Skip to content

Commit

Permalink
Named mutex: Use flock instead of pthread process-shared mutex in som…
Browse files Browse the repository at this point in the history
…e more cases

Workaround for #5456:
- Sometimes, a timed wait operation is not getting released, causing a hang
- Due to the hang, it is not possible to detect this issue with code
- Temporarily disabled the use of pthread process-shared mutexes on ARM/ARM64. File locks will be used instead.

Workaround for #5872:
- On Alpine Linux, a pthread process-shared robust mutex is detecting the case where a process abandons the mutex when it exits while holding the lock, but is putting the mutex into an unrecoverable state (ENOTRECOVERABLE) instead of assigning lock ownership to the next thread that is released from a wait for a lock and notifying of abandonment (EOWNERDEAD).
- Added a test case to detect this issue, to have it use file locks instead

Close #5456
  • Loading branch information
kouvel committed Jul 5, 2016
1 parent 7782e7d commit f638167
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 49 deletions.
1 change: 1 addition & 0 deletions src/pal/src/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
#cmakedefine01 HAS_FTRUNCATE_LENGTH_ISSUE
#cmakedefine01 UNWIND_CONTEXT_IS_UCONTEXT_T
#cmakedefine01 HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
#cmakedefine01 HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES
#cmakedefine BSD_REGS_STYLE(reg, RR, rr) @BSD_REGS_STYLE@
#cmakedefine01 HAVE_SCHED_OTHER_ASSIGNABLE

Expand Down
189 changes: 189 additions & 0 deletions src/pal/src/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,195 @@ int main()
}" 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)
Expand Down
32 changes: 20 additions & 12 deletions src/pal/src/include/pal/mutex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ DWORD SPINLOCKTryAcquire (LONG * lock);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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,
Expand All @@ -83,7 +91,7 @@ enum class MutexTryAcquireLockResult
TimedOut
};

#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES
#if NAMED_MUTEX_USE_PTHREAD_MUTEX
class MutexHelpers
{
public:
Expand All @@ -93,16 +101,16 @@ class MutexHelpers
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;
Expand All @@ -111,15 +119,15 @@ class NamedMutexSharedData
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;
Expand All @@ -142,10 +150,10 @@ class NamedMutexProcessData : public SharedMemoryProcessDataBase
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;

Expand All @@ -158,10 +166,10 @@ class NamedMutexProcessData : public SharedMemoryProcessDataBase
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;

Expand Down
Loading

0 comments on commit f638167

Please sign in to comment.