From 107d7ca08d4dc209760c91e375a18df5afe7fafe Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 2 Feb 2019 20:14:33 +0100 Subject: [PATCH 1/3] Add low-level monitor API to System.Native --- src/Native/Unix/Common/pal_config.h.in | 1 + src/Native/Unix/System.Native/CMakeLists.txt | 1 + src/Native/Unix/System.Native/pal_threading.c | 167 ++++++++++++++++++ src/Native/Unix/System.Native/pal_threading.h | 72 ++++++++ src/Native/Unix/configure.cmake | 3 + 5 files changed, 244 insertions(+) create mode 100644 src/Native/Unix/System.Native/pal_threading.c create mode 100644 src/Native/Unix/System.Native/pal_threading.h diff --git a/src/Native/Unix/Common/pal_config.h.in b/src/Native/Unix/Common/pal_config.h.in index d372ed825d3e..01fb335d6f30 100644 --- a/src/Native/Unix/Common/pal_config.h.in +++ b/src/Native/Unix/Common/pal_config.h.in @@ -64,6 +64,7 @@ #cmakedefine01 HAVE_LINUX_RTNETLINK_H #cmakedefine01 HAVE_GETDOMAINNAME_SIZET #cmakedefine01 HAVE_INOTIFY +#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK #cmakedefine01 HAVE_CLOCK_MONOTONIC #cmakedefine01 HAVE_CLOCK_REALTIME #cmakedefine01 HAVE_MACH_ABSOLUTE_TIME diff --git a/src/Native/Unix/System.Native/CMakeLists.txt b/src/Native/Unix/System.Native/CMakeLists.txt index 4251bc5012f0..3926db9f2d0c 100644 --- a/src/Native/Unix/System.Native/CMakeLists.txt +++ b/src/Native/Unix/System.Native/CMakeLists.txt @@ -17,6 +17,7 @@ set(NATIVE_SOURCES pal_signal.c pal_string.c pal_tcpstate.c + pal_threading.c pal_time.c pal_uid.c pal_datetime.c diff --git a/src/Native/Unix/System.Native/pal_threading.c b/src/Native/Unix/System.Native/pal_threading.c new file mode 100644 index 000000000000..a15c8a33a7db --- /dev/null +++ b/src/Native/Unix/System.Native/pal_threading.c @@ -0,0 +1,167 @@ +// 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. + +#include "pal_config.h" +#include "pal_threading.h" +#include "pal_utilities.h" + +#include +#include +#include + +struct Monitor +{ + pthread_mutex_t Mutex; + pthread_cond_t Condition; +}; + +enum +{ + SecondsToNanoSeconds = 1000000000, // 10^9 + MilliSecondsToNanoSeconds = 1000000 // 10^6 +}; + +#ifdef NDEBUG +#define assert_no_error(err) ((void)(err)) +#else +#define assert_no_error(err) assert((err) == 0) +#endif + +Monitor *SystemNative_MonitorNew(void) +{ + Monitor *monitor; + int error, initError; + + monitor = (Monitor *)malloc(sizeof(struct Monitor)); + if (monitor == NULL) + { + return NULL; + } + + error = pthread_mutex_init(&monitor->Mutex, NULL); + if (error != 0) + { + free(monitor); + return NULL; + } + +#if HAVE_MACH_ABSOLUTE_TIME + // Older versions of OSX don't support CLOCK_MONOTONIC, so we don't use pthread_condattr_setclock. See + // Wait(int32_t timeoutMilliseconds). + initError = pthread_cond_init(&monitor->Condition, NULL); +#elif HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC + pthread_condattr_t conditionAttributes; + error = pthread_condattr_init(&conditionAttributes); + if (error != 0) + { + error = pthread_mutex_destroy(&monitor->Mutex); + assert_no_error(error); + free(monitor); + return NULL; + } + + error = pthread_condattr_setclock(&conditionAttributes, CLOCK_MONOTONIC); + assert_no_error(error); + + initError = pthread_cond_init(&monitor->Condition, &conditionAttributes); + + error = pthread_condattr_destroy(&conditionAttributes); + assert_no_error(error); +#else + #error "Don't know how to perform timed wait on this platform" +#endif + + if (initError != 0) + { + error = pthread_mutex_destroy(&monitor->Mutex); + assert_no_error(error); + free(monitor); + return NULL; + } + + return monitor; +} + +void SystemNative_MonitorDelete(Monitor *monitor) +{ + int error; + + error = pthread_mutex_destroy(&monitor->Mutex); + assert_no_error(error); + error = pthread_cond_destroy(&monitor->Condition); + assert_no_error(error); + free(monitor); +} + +void SystemNative_MonitorAcquire(Monitor *monitor) +{ + int error = pthread_mutex_lock(&monitor->Mutex); + assert_no_error(error); +} + +int32_t SystemNative_MonitorTryAcquire(Monitor *monitor) +{ + int error = pthread_mutex_trylock(&monitor->Mutex); + assert(error == 0 || error == EBUSY); + return error == 0; +} + +void SystemNative_MonitorRelease(Monitor *monitor) +{ + int error = pthread_mutex_unlock(&monitor->Mutex); + assert_no_error(error); +} + +void SystemNative_MonitorWait(Monitor *monitor) +{ + int error = pthread_cond_wait(&monitor->Condition, &monitor->Mutex); + assert_no_error(error); +} + +int32_t SystemNative_MonitorTimedWait(Monitor *monitor, int32_t timeoutMilliseconds) +{ + int error; + + assert(timeoutMilliseconds >= -1); + if (timeoutMilliseconds < 0) + { + SystemNative_MonitorWait(monitor); + return 1; + } + + // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with + // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. + struct timespec timeout; +#if HAVE_MACH_ABSOLUTE_TIME + timeout.tv_sec = (time_t)(timeoutMilliseconds / 1000); + timeout.tv_nsec = (long)((timeoutMilliseconds % 1000) * MilliSecondsToNanoSeconds); + error = pthread_cond_timedwait_relative_np(&monitor->Condition, &monitor->Mutex, &timeout); +#else + uint64_t nanoseconds; + error = clock_gettime(CLOCK_MONOTONIC, &timeout); + assert_no_error(error); + nanoseconds = ((uint64_t)timeout.tv_sec * SecondsToNanoSeconds) + (uint64_t)timeout.tv_nsec; + nanoseconds += (uint64_t)timeoutMilliseconds * MilliSecondsToNanoSeconds; + timeout.tv_sec = (time_t)(nanoseconds / SecondsToNanoSeconds); + timeout.tv_nsec = (long)(nanoseconds % SecondsToNanoSeconds); + error = pthread_cond_timedwait(&monitor->Condition, &monitor->Mutex, &timeout); +#endif + assert(error == 0 || error == ETIMEDOUT); + + return error == 0; +} + +void SystemNative_MonitorSignalAndRelease(Monitor *monitor) +{ + int error = pthread_cond_signal(&monitor->Condition); + assert_no_error(error); + SystemNative_MonitorRelease(monitor); +} + +void SystemNative_MonitorBroadcastAndRelease(Monitor *monitor) +{ + int error = pthread_cond_broadcast(&monitor->Condition); + assert(error == 0); + SystemNative_MonitorRelease(monitor); +} diff --git a/src/Native/Unix/System.Native/pal_threading.h b/src/Native/Unix/System.Native/pal_threading.h new file mode 100644 index 000000000000..3f2c432569a5 --- /dev/null +++ b/src/Native/Unix/System.Native/pal_threading.h @@ -0,0 +1,72 @@ +// 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. + +#pragma once + +#include "pal_compiler.h" +#include "pal_types.h" + +/** + * An opaque type representing the low-level monitor. This is equivalent to + * POSIX threads mutex and condition variable bundled together. + */ +typedef struct Monitor Monitor; + +/** + * Allocates a new low-level monitor object and initializes it. + * + * Return NULL if the allocation failed, otherwise an opaque object representing the monitor. + */ +DLLEXPORT Monitor *SystemNative_MonitorNew(void); + +/** + * Deallocates the low-level monitor allocated through SystemNative_MonitorNew. + */ +DLLEXPORT void SystemNative_MonitorDelete(Monitor *monitor); + +/** + * Acquires the lock on the low-level monitor object. + */ +DLLEXPORT void SystemNative_MonitorAcquire(Monitor *monitor); + +/** + * Attempts to acquire the lock on the low-level monitor object. + * + * Returns 1 if the lock is acquired, or 0 if it was already held by someone else. + */ +DLLEXPORT int32_t SystemNative_MonitorTryAcquire(Monitor *monitor); + +/** + * Releases the lock on the low-level monitor object. + */ +DLLEXPORT void SystemNative_MonitorRelease(Monitor *monitor); + +/** + * Releases the lock on an object and blocks the current thread until the + * monitor is signalled, then it reacquires the lock. + */ +DLLEXPORT void SystemNative_MonitorWait(Monitor *monitor); + +/** + * Releases the lock on an object and blocks the current thread until the + * monitor is signalled, then it reacquires the lock. If the specified time-out + * interval elapses, the wait is aborted. + * + * Returns 1 if the wait succeeded, or 0 if it timed out. + */ +DLLEXPORT int32_t SystemNative_MonitorTimedWait(Monitor *monitor, int32_t timeoutMilliseconds); + +/** + * Wakes one thread in the waiting queue (enqueued by either SystemNative_MonitorWait or + * SystemNative_MonitorTimedWait) and releases the lock. Unlike Monitor.Pulse in + * managed code it doesn't reacquire the lock. + */ +DLLEXPORT void SystemNative_MonitorSignalAndRelease(Monitor *monitor); + +/** + * Wakes all threads in the waiting queue (enqueued by either SystemNative_MonitorWait or + * SystemNative_MonitorTimedWait) and releases the lock. Unlike Monitor.PulseAll in + * managed code it doesn't reacquire the lock. + */ +DLLEXPORT void SystemNative_MonitorBroadcastAndRelease(Monitor *monitor); diff --git a/src/Native/Unix/configure.cmake b/src/Native/Unix/configure.cmake index a76aef06bd2e..f36656fb1b82 100644 --- a/src/Native/Unix/configure.cmake +++ b/src/Native/Unix/configure.cmake @@ -6,6 +6,7 @@ include(CheckPrototypeDefinition) include(CheckStructHasMember) include(CheckSymbolExists) include(CheckTypeSize) +include(CheckLibraryExists) if (CMAKE_SYSTEM_NAME STREQUAL Linux) set(PAL_UNIX_NAME \"LINUX\") @@ -386,6 +387,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux) endif () +check_library_exists(pthread pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) + check_c_source_runs( " #include From e21e356c47fe60794dcb91c902a93df0395f4b59 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 2 Feb 2019 20:57:43 +0100 Subject: [PATCH 2/3] Add P/Invoke signatures --- .../Unix/System.Native/Interop.Monitor.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs diff --git a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs new file mode 100644 index 000000000000..3b4bdea87438 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs @@ -0,0 +1,39 @@ +// 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. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal unsafe partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorNew")] + internal static extern IntPtr MonitorNew(); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorDelete")] + internal static extern void MonitorDelete(IntPtr monitor); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorAcquire")] + internal static extern void MonitorAcquire(IntPtr mutex); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorTryAcquire")] + internal static extern int MonitorTryAcquire(IntPtr mutex); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorRelease")] + internal static extern void MonitorRelease(IntPtr mutex); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorWait")] + internal static extern void MonitorWait(IntPtr monitor); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorTimedWait")] + internal static extern bool MonitorTimedWait(IntPtr monitor, int timeoutMilliseconds); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorSignalAndRelease")] + internal static extern void MonitorSignalAndRelease(IntPtr monitor); + + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorBroadcastAndRelease")] + internal static extern void MonitorBroadcastAndRelease(IntPtr monitor); + } +} \ No newline at end of file From 2e1eab05c489a7ae866881949b23e04516b99341 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 2 Feb 2019 21:41:19 +0100 Subject: [PATCH 3/3] Remove two unused APIs --- .../Interop/Unix/System.Native/Interop.Monitor.cs | 8 +------- src/Native/Unix/System.Native/pal_threading.c | 14 -------------- src/Native/Unix/System.Native/pal_threading.h | 14 -------------- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs index 3b4bdea87438..d203ca12b97b 100644 --- a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs +++ b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs @@ -18,9 +18,6 @@ internal unsafe partial class Sys [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorAcquire")] internal static extern void MonitorAcquire(IntPtr mutex); - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorTryAcquire")] - internal static extern int MonitorTryAcquire(IntPtr mutex); - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorRelease")] internal static extern void MonitorRelease(IntPtr mutex); @@ -32,8 +29,5 @@ internal unsafe partial class Sys [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorSignalAndRelease")] internal static extern void MonitorSignalAndRelease(IntPtr monitor); - - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MonitorBroadcastAndRelease")] - internal static extern void MonitorBroadcastAndRelease(IntPtr monitor); - } + } } \ No newline at end of file diff --git a/src/Native/Unix/System.Native/pal_threading.c b/src/Native/Unix/System.Native/pal_threading.c index a15c8a33a7db..74d559249059 100644 --- a/src/Native/Unix/System.Native/pal_threading.c +++ b/src/Native/Unix/System.Native/pal_threading.c @@ -100,13 +100,6 @@ void SystemNative_MonitorAcquire(Monitor *monitor) assert_no_error(error); } -int32_t SystemNative_MonitorTryAcquire(Monitor *monitor) -{ - int error = pthread_mutex_trylock(&monitor->Mutex); - assert(error == 0 || error == EBUSY); - return error == 0; -} - void SystemNative_MonitorRelease(Monitor *monitor) { int error = pthread_mutex_unlock(&monitor->Mutex); @@ -158,10 +151,3 @@ void SystemNative_MonitorSignalAndRelease(Monitor *monitor) assert_no_error(error); SystemNative_MonitorRelease(monitor); } - -void SystemNative_MonitorBroadcastAndRelease(Monitor *monitor) -{ - int error = pthread_cond_broadcast(&monitor->Condition); - assert(error == 0); - SystemNative_MonitorRelease(monitor); -} diff --git a/src/Native/Unix/System.Native/pal_threading.h b/src/Native/Unix/System.Native/pal_threading.h index 3f2c432569a5..3867d12b09db 100644 --- a/src/Native/Unix/System.Native/pal_threading.h +++ b/src/Native/Unix/System.Native/pal_threading.h @@ -30,13 +30,6 @@ DLLEXPORT void SystemNative_MonitorDelete(Monitor *monitor); */ DLLEXPORT void SystemNative_MonitorAcquire(Monitor *monitor); -/** - * Attempts to acquire the lock on the low-level monitor object. - * - * Returns 1 if the lock is acquired, or 0 if it was already held by someone else. - */ -DLLEXPORT int32_t SystemNative_MonitorTryAcquire(Monitor *monitor); - /** * Releases the lock on the low-level monitor object. */ @@ -63,10 +56,3 @@ DLLEXPORT int32_t SystemNative_MonitorTimedWait(Monitor *monitor, int32_t timeou * managed code it doesn't reacquire the lock. */ DLLEXPORT void SystemNative_MonitorSignalAndRelease(Monitor *monitor); - -/** - * Wakes all threads in the waiting queue (enqueued by either SystemNative_MonitorWait or - * SystemNative_MonitorTimedWait) and releases the lock. Unlike Monitor.PulseAll in - * managed code it doesn't reacquire the lock. - */ -DLLEXPORT void SystemNative_MonitorBroadcastAndRelease(Monitor *monitor);