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..d203ca12b97b --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.Monitor.cs @@ -0,0 +1,33 @@ +// 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_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); + } +} \ No newline at end of file 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..74d559249059 --- /dev/null +++ b/src/Native/Unix/System.Native/pal_threading.c @@ -0,0 +1,153 @@ +// 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); +} + +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); +} 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..3867d12b09db --- /dev/null +++ b/src/Native/Unix/System.Native/pal_threading.h @@ -0,0 +1,58 @@ +// 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); + +/** + * 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); 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