summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Vorlicek <janvorli@microsoft.com>2017-04-26 06:48:20 -0700
committerGitHub <noreply@github.com>2017-04-26 06:48:20 -0700
commita5a8c6cdc6e3b732fc5a344878b165f149d99729 (patch)
tree2131c4a28caca300a6cef99d529a734699f52348
parentd3d2efd166c408d59cde076c674fd140e7313e4d (diff)
downloadcoreclr-a5a8c6cdc6e3b732fc5a344878b165f149d99729.tar.gz
coreclr-a5a8c6cdc6e3b732fc5a344878b165f149d99729.tar.bz2
coreclr-a5a8c6cdc6e3b732fc5a344878b165f149d99729.zip
Fix thread wait to be resilient to real time clock changes on Unix (#11213)
This change fixes a problem that happens when a thread uses a timed wait and the real time clock change in the system (e.g. due to the user action, time server sync, leap second, etc). In such case, the wait may end up waiting for shorter or longer period of time. It fixes the issue for Thread.Sleep and timed wait on a process local synchronization primitives by using CLOCK_MONOTONIC instead of CLOCK_REALTIME for waiting on cond vars. Unfortunately, the waits on cross process synchronication primitives like named mutexes always use CLOCK_REALTIME, so this fix works on process local primitives only.
-rw-r--r--src/pal/src/config.h.in1
-rw-r--r--src/pal/src/configure.cmake3
-rw-r--r--src/pal/src/synchmgr/synchmanager.cpp81
-rw-r--r--src/pal/src/synchmgr/synchmanager.hpp3
-rw-r--r--src/pal/src/synchobj/mutex.cpp2
5 files changed, 72 insertions, 18 deletions
diff --git a/src/pal/src/config.h.in b/src/pal/src/config.h.in
index e3024ac4d5..7f37f42222 100644
--- a/src/pal/src/config.h.in
+++ b/src/pal/src/config.h.in
@@ -103,6 +103,7 @@
#cmakedefine01 HAVE_CLOCK_MONOTONIC_COARSE
#cmakedefine01 HAVE_MACH_ABSOLUTE_TIME
#cmakedefine01 HAVE_CLOCK_THREAD_CPUTIME
+#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK
#cmakedefine01 STATVFS64_PROTOTYPE_BROKEN
#cmakedefine01 HAVE_MMAP_DEV_ZERO
#cmakedefine01 MMAP_ANON_IGNORES_PROTECTION
diff --git a/src/pal/src/configure.cmake b/src/pal/src/configure.cmake
index f9a23e8e60..b5b98d5b2d 100644
--- a/src/pal/src/configure.cmake
+++ b/src/pal/src/configure.cmake
@@ -417,6 +417,9 @@ int main()
exit(ret);
}" HAVE_CLOCK_MONOTONIC)
+
+check_library_exists(pthread pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK)
+
check_cxx_source_runs("
#include <stdlib.h>
#include <time.h>
diff --git a/src/pal/src/synchmgr/synchmanager.cpp b/src/pal/src/synchmgr/synchmanager.cpp
index 3aec140474..d836a177bb 100644
--- a/src/pal/src/synchmgr/synchmanager.cpp
+++ b/src/pal/src/synchmgr/synchmanager.cpp
@@ -450,7 +450,7 @@ namespace CorUnix
if (dwTimeout != INFINITE)
{
// Calculate absolute timeout
- palErr = GetAbsoluteTimeout(dwTimeout, &tsAbsTmo);
+ palErr = GetAbsoluteTimeout(dwTimeout, &tsAbsTmo, /*fPreferMonotonicClock*/ TRUE);
if (NO_ERROR != palErr)
{
ERROR("Failed to convert timeout to absolute timeout\n");
@@ -1572,7 +1572,7 @@ namespace CorUnix
ptnwdWorkerThreadNativeData =
&pSynchManager->m_pthrWorker->synchronizationInfo.m_tnwdNativeData;
- palErr = GetAbsoluteTimeout(WorkerThreadTerminationTimeout, &tsAbsTmo);
+ palErr = GetAbsoluteTimeout(WorkerThreadTerminationTimeout, &tsAbsTmo, /*fPreferMonotonicClock*/ TRUE);
if (NO_ERROR != palErr)
{
ERROR("Failed to convert timeout to absolute timeout\n");
@@ -4078,6 +4078,9 @@ namespace CorUnix
int iRet;
const int MaxUnavailableResourceRetries = 10;
int iEagains;
+ pthread_condattr_t attrs;
+ pthread_condattr_t *attrsPtr = nullptr;
+
m_shridWaitAwakened = RawSharedObjectAlloc(sizeof(DWORD),
DefaultSharedPool);
if (NULLSharedID == m_shridWaitAwakened)
@@ -4096,6 +4099,36 @@ namespace CorUnix
VolatileStore<DWORD>(pdwWaitState, TWS_ACTIVE);
m_tsThreadState = TS_STARTING;
+#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
+ attrsPtr = &attrs;
+ iRet = pthread_condattr_init(&attrs);
+ if (0 != iRet)
+ {
+ ERROR("Failed to initialize thread synchronization condition attribute "
+ "[error=%d (%s)]\n", iRet, strerror(iRet));
+ if (ENOMEM == iRet)
+ {
+ palErr = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ else
+ {
+ palErr = ERROR_INTERNAL_ERROR;
+ }
+ goto IPrC_exit;
+ }
+
+ // Ensure that the pthread_cond_timedwait will use CLOCK_MONOTONIC
+ iRet = pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
+ if (0 != iRet)
+ {
+ ERROR("Failed set thread synchronization condition timed wait clock "
+ "[error=%d (%s)]\n", iRet, strerror(iRet));
+ palErr = ERROR_INTERNAL_ERROR;
+ pthread_condattr_destroy(&attrs);
+ goto IPrC_exit;
+ }
+#endif // HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
+
iEagains = 0;
Mutex_retry:
iRet = pthread_mutex_init(&m_tnwdNativeData.mutex, NULL);
@@ -4121,7 +4154,9 @@ namespace CorUnix
iEagains = 0;
Cond_retry:
- iRet = pthread_cond_init(&m_tnwdNativeData.cond, NULL);
+
+ iRet = pthread_cond_init(&m_tnwdNativeData.cond, attrsPtr);
+
if (0 != iRet)
{
ERROR("Failed creating thread synchronization condition "
@@ -4146,6 +4181,10 @@ namespace CorUnix
m_tnwdNativeData.fInitialized = true;
IPrC_exit:
+ if (attrsPtr != nullptr)
+ {
+ pthread_condattr_destroy(attrsPtr);
+ }
if (NO_ERROR != palErr)
{
m_tsThreadState = TS_FAILED;
@@ -4515,27 +4554,37 @@ namespace CorUnix
Converts a relative timeout to an absolute one.
--*/
- PAL_ERROR CPalSynchronizationManager::GetAbsoluteTimeout(DWORD dwTimeout, struct timespec * ptsAbsTmo)
+ PAL_ERROR CPalSynchronizationManager::GetAbsoluteTimeout(DWORD dwTimeout, struct timespec * ptsAbsTmo, BOOL fPreferMonotonicClock)
{
PAL_ERROR palErr = NO_ERROR;
int iRet;
-#if HAVE_WORKING_CLOCK_GETTIME
- // Not every platform implements a (working) clock_gettime
- iRet = clock_gettime(CLOCK_REALTIME, ptsAbsTmo);
-#elif HAVE_WORKING_GETTIMEOFDAY
- // Not every platform implements a (working) gettimeofday
- struct timeval tv;
- iRet = gettimeofday(&tv, NULL);
- if (0 == iRet)
+#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
+ if (fPreferMonotonicClock)
{
- ptsAbsTmo->tv_sec = tv.tv_sec;
- ptsAbsTmo->tv_nsec = tv.tv_usec * tccMicroSecondsToNanoSeconds;
+ iRet = clock_gettime(CLOCK_MONOTONIC, ptsAbsTmo);
}
+ else
+ {
+#endif
+#if HAVE_WORKING_CLOCK_GETTIME
+ // Not every platform implements a (working) clock_gettime
+ iRet = clock_gettime(CLOCK_REALTIME, ptsAbsTmo);
+#elif HAVE_WORKING_GETTIMEOFDAY
+ // Not every platform implements a (working) gettimeofday
+ struct timeval tv;
+ iRet = gettimeofday(&tv, NULL);
+ if (0 == iRet)
+ {
+ ptsAbsTmo->tv_sec = tv.tv_sec;
+ ptsAbsTmo->tv_nsec = tv.tv_usec * tccMicroSecondsToNanoSeconds;
+ }
#else
- #error "Don't know how to get hi-res current time on this platform"
+ #error "Don't know how to get hi-res current time on this platform"
#endif // HAVE_WORKING_CLOCK_GETTIME, HAVE_WORKING_GETTIMEOFDAY
-
+#if HAVE_CLOCK_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
+ }
+#endif
if (0 == iRet)
{
ptsAbsTmo->tv_sec += dwTimeout / tccSecondsToMillieSeconds;
diff --git a/src/pal/src/synchmgr/synchmanager.hpp b/src/pal/src/synchmgr/synchmanager.hpp
index fdef82e936..883d5b8b61 100644
--- a/src/pal/src/synchmgr/synchmanager.hpp
+++ b/src/pal/src/synchmgr/synchmanager.hpp
@@ -1015,7 +1015,8 @@ namespace CorUnix
static PAL_ERROR GetAbsoluteTimeout(
DWORD dwTimeout,
- struct timespec * ptsAbsTmo);
+ struct timespec * ptsAbsTmo,
+ BOOL fPreferMonotonicClock);
};
}
diff --git a/src/pal/src/synchobj/mutex.cpp b/src/pal/src/synchobj/mutex.cpp
index d929eaa472..692f5e2ade 100644
--- a/src/pal/src/synchobj/mutex.cpp
+++ b/src/pal/src/synchobj/mutex.cpp
@@ -859,7 +859,7 @@ MutexTryAcquireLockResult MutexHelpers::TryAcquireLock(pthread_mutex_t *mutex, D
default:
{
struct timespec timeoutTime;
- PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime);
+ PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime, /*fPreferMonotonicClock*/ FALSE);
_ASSERTE(palError == NO_ERROR);
lockResult = pthread_mutex_timedlock(mutex, &timeoutTime);
break;