[Local GC] Move operations on CLREventStatic to the EE interface (dotnet/coreclr...
authorSean Gillespie <sean@swgillespie.me>
Fri, 14 Apr 2017 16:51:10 +0000 (09:51 -0700)
committerGitHub <noreply@github.com>
Fri, 14 Apr 2017 16:51:10 +0000 (09:51 -0700)
* [Local GC] Move operations on CLREventStatic to the EE and add their functionality to the interface

* Fix a missed case

* Split GetWaitForGCEvent into two smaller interface methods to avoid exposing the event itself on the interface

* Initial implementation for Unix

* Complete unix implementation

* Make it work on Windows

* Remove redudant methods from GCToEEInterface

* Fix the Linux build

* First part of code review feedback: make GCEvent dispatch statically (Windows)

* Second part of code review feedback: make GCEvent dispatch statically (Unix)

* Standardize implementation across Windows/Unix (apparently MSVC is more lenient about constructor names than clang)

* Address code review feedback: Add Create*Event methods back onto GCEvent and remove them from GCToOSInterface

* Address code review feedback: remove a dead define

* Remove a bad comment, remove an unnecessary friend class, fix some formatting issues

* Fix an issue when initializing a GCEvent on Linux (should not be allocating an Impl in the constructor)

* Fix the same issue on Windows (less bad, just leaks memory instead of asserting)

Commit migrated from https://github.com/dotnet/coreclr/commit/13812bfd0f6eda442c944f416e8c871b18d99e3f

15 files changed:
src/coreclr/src/gc/env/gcenv.os.h
src/coreclr/src/gc/gc.cpp
src/coreclr/src/gc/gcee.cpp
src/coreclr/src/gc/gcimpl.h
src/coreclr/src/gc/gcinterface.h
src/coreclr/src/gc/gcpriv.h
src/coreclr/src/gc/unix/CMakeLists.txt
src/coreclr/src/gc/unix/config.h.in
src/coreclr/src/gc/unix/configure.cmake
src/coreclr/src/gc/unix/events.cpp [new file with mode: 0644]
src/coreclr/src/gc/unix/gcenv.unix.cpp
src/coreclr/src/gc/unix/globals.h [new file with mode: 0644]
src/coreclr/src/gc/windows/gcenv.windows.cpp
src/coreclr/src/vm/gcenv.os.cpp
src/coreclr/src/vm/threadsuspend.cpp

index 6a126f2..6ea35e3 100644 (file)
@@ -47,6 +47,81 @@ struct GCThreadAffinity
     int Processor;
 };
 
+// An event is a synchronization object whose state can be set and reset
+// indicating that an event has occured. It is used pervasively throughout
+// the GC.
+class GCEvent {
+private:
+    class Impl;
+    Impl *m_impl;
+
+public:
+    // Constructs a new uninitialized event.
+    GCEvent();
+
+    // Destructs an event.
+    ~GCEvent();
+
+    // Closes the event. Attempting to use the event past calling CloseEvent
+    // is a logic error.
+    void CloseEvent();
+
+    // "Sets" the event, indicating that a particular event has occured. May
+    // wake up other threads waiting on this event. Depending on whether or
+    // not this event is an auto-reset event, the state of the event may
+    // or may not be automatically reset after Set is called.
+    void Set();
+
+    // Resets the event, resetting it back to a non-signalled state. Auto-reset
+    // events automatically reset once the event is set, while manual-reset
+    // events do not reset until Reset is called. It is a no-op to call Reset
+    // on an auto-reset event.
+    void Reset();
+
+    // Waits for some period of time for this event to be signalled. The
+    // period of time may be infinite (if the timeout argument is INFINITE) or
+    // it may be a specified period of time, in milliseconds.
+    // Returns:
+    //   One of three values, depending on how why this thread was awoken:
+    //      WAIT_OBJECT_0 - This event was signalled and woke up this thread.
+    //      WAIT_TIMEOUT  - The timeout interval expired without this event being signalled.
+    //      WAIT_FAILED   - The wait failed.
+    uint32_t Wait(uint32_t timeout, bool alertable);
+
+    // Determines whether or not this event is valid.
+    // Returns:
+    //  true if this event is invalid (i.e. it has not yet been initialized or
+    //  has already been closed), false otherwise
+    bool IsValid() const
+    {
+        return m_impl != nullptr;
+    }
+
+    // Initializes this event to be a host-aware manual reset event with the
+    // given initial state.
+    // Returns:
+    //   true if the initialization succeeded, false if it did not
+    bool CreateManualEventNoThrow(bool initialState);
+
+    // Initializes this event to be a host-aware auto-resetting event with the
+    // given initial state.
+    // Returns:
+    //   true if the initialization succeeded, false if it did not
+    bool CreateAutoEventNoThrow(bool initialState);
+
+    // Initializes this event to be a host-unaware manual reset event with the
+    // given initial state.
+    // Returns:
+    //   true if the initialization succeeded, false if it did not
+    bool CreateOSManualEventNoThrow(bool initialState);
+
+    // Initializes this event to be a host-unaware auto-resetting event with the
+    // given initial state.
+    // Returns:
+    //   true if the initialization succeeded, false if it did not
+    bool CreateOSAutoEventNoThrow(bool initialState);
+};
+
 // GC thread function prototype
 typedef void (*GCThreadFunction)(void* param);
 
index 1aba30b..6d53c89 100644 (file)
@@ -621,7 +621,7 @@ enum gc_join_flavor
 #define first_thread_arrived 2
 struct join_structure
 {
-    CLREvent joined_event[3]; // the last event in the array is only used for first_thread_arrived.
+    GCEvent joined_event[3]; // the last event in the array is only used for first_thread_arrived.
     VOLATILE(int32_t) join_lock;
     VOLATILE(int32_t) r_join_lock;
     VOLATILE(int32_t) join_restart;
@@ -1201,8 +1201,8 @@ class recursive_gc_sync
     static VOLATILE(BOOL) gc_background_running; //initial state FALSE
     static VOLATILE(int32_t) foreground_count; // initial state 0;
     static VOLATILE(uint32_t) foreground_gate; // initial state FALSE;
-    static CLREvent foreground_complete;//Auto Reset
-    static CLREvent foreground_allowed;//Auto Reset
+    static GCEvent foreground_complete;//Auto Reset
+    static GCEvent foreground_allowed;//Auto Reset
 public:
     static void begin_background();
     static void end_background();
@@ -1218,8 +1218,8 @@ VOLATILE(int32_t) recursive_gc_sync::foreground_request_count = 0;//initial stat
 VOLATILE(int32_t) recursive_gc_sync::foreground_count = 0; // initial state 0;
 VOLATILE(BOOL) recursive_gc_sync::gc_background_running = FALSE; //initial state FALSE
 VOLATILE(uint32_t) recursive_gc_sync::foreground_gate = 0;
-CLREvent recursive_gc_sync::foreground_complete;//Auto Reset
-CLREvent recursive_gc_sync::foreground_allowed;//Manual Reset
+GCEvent recursive_gc_sync::foreground_complete;//Auto Reset
+GCEvent recursive_gc_sync::foreground_allowed;//Manual Reset
 
 BOOL recursive_gc_sync::init ()
 {
@@ -2308,7 +2308,7 @@ sorted_table* gc_heap::seg_table;
 #endif //!SEG_MAPPING_TABLE || FEATURE_BASICFREEZE
 
 #ifdef MULTIPLE_HEAPS
-CLREvent    gc_heap::ee_suspend_event;
+GCEvent     gc_heap::ee_suspend_event;
 size_t      gc_heap::min_balance_threshold = 0;
 #endif //MULTIPLE_HEAPS
 
@@ -2316,7 +2316,7 @@ VOLATILE(BOOL) gc_heap::gc_started;
 
 #ifdef MULTIPLE_HEAPS
 
-CLREvent    gc_heap::gc_start_event;
+GCEvent     gc_heap::gc_start_event;
 
 bool        gc_heap::gc_thread_no_affinitize_p = false;
 
@@ -2385,13 +2385,13 @@ uint64_t    gc_heap::total_physical_mem;
 uint64_t    gc_heap::entry_available_physical_mem;
 
 #ifdef BACKGROUND_GC
-CLREvent    gc_heap::bgc_start_event;
+GCEvent     gc_heap::bgc_start_event;
 
 gc_mechanisms gc_heap::saved_bgc_settings;
 
-CLREvent    gc_heap::background_gc_done_event;
+GCEvent     gc_heap::background_gc_done_event;
 
-CLREvent    gc_heap::ee_proceed_event;
+GCEvent     gc_heap::ee_proceed_event;
 
 bool        gc_heap::gc_can_use_concurrent = false;
 
@@ -2403,7 +2403,7 @@ BOOL        gc_heap::dont_restart_ee_p = FALSE;
 
 BOOL        gc_heap::keep_bgc_threads_p = FALSE;
 
-CLREvent    gc_heap::bgc_threads_sync_event;
+GCEvent     gc_heap::bgc_threads_sync_event;
 
 BOOL        gc_heap::do_ephemeral_gc_p = FALSE;
 
@@ -2589,7 +2589,7 @@ BOOL    gc_heap::bgc_thread_running;
 
 CLRCriticalSection gc_heap::bgc_threads_timeout_cs;
 
-CLREvent gc_heap::gc_lh_block_event;
+GCEvent gc_heap::gc_lh_block_event;
 
 #endif //BACKGROUND_GC
 
@@ -2685,9 +2685,9 @@ int                    gc_heap::loh_pinned_queue_decay = LOH_PIN_DECAY;
 
 #endif //FEATURE_LOH_COMPACTION
 
-CLREvent gc_heap::full_gc_approach_event;
+GCEvent gc_heap::full_gc_approach_event;
 
-CLREvent gc_heap::full_gc_end_event;
+GCEvent gc_heap::full_gc_end_event;
 
 uint32_t gc_heap::fgn_maxgen_percent = 0;
 
@@ -10310,7 +10310,7 @@ gc_heap::loh_state_info gc_heap::last_loh_states[max_saved_loh_states];
 
 VOLATILE(int32_t) gc_heap::gc_done_event_lock;
 VOLATILE(bool) gc_heap::gc_done_event_set;
-CLREvent gc_heap::gc_done_event;
+GCEvent gc_heap::gc_done_event;
 #endif //!MULTIPLE_HEAPS
 VOLATILE(bool) gc_heap::internal_gc_done;
 
@@ -11739,7 +11739,7 @@ void gc_heap::send_full_gc_notification (int gen_num, BOOL due_to_alloc_p)
     }
 }
 
-wait_full_gc_status gc_heap::full_gc_wait (CLREvent *event, int time_out_ms)
+wait_full_gc_status gc_heap::full_gc_wait (GCEvent *event, int time_out_ms)
 {
     if (fgn_maxgen_percent == 0)
     {
@@ -32413,7 +32413,7 @@ void gc_heap::descr_generations (BOOL begin_gc_p)
 VOLATILE(BOOL)    GCHeap::GcInProgress            = FALSE;
 //GCTODO
 //CMCSafeLock*      GCHeap::fGcLock;
-CLREvent            *GCHeap::WaitForGCEvent         = NULL;
+GCEvent            *GCHeap::WaitForGCEvent         = NULL;
 //GCTODO
 #ifdef TRACE_GC
 unsigned int       GCHeap::GcDuration;
@@ -33687,7 +33687,7 @@ HRESULT GCHeap::Initialize ()
     gc_heap::youngest_gen_desired_th = gc_heap::mem_one_percent;
 #endif // BIT64
 
-    WaitForGCEvent = new (nothrow) CLREvent;
+    WaitForGCEvent = new (nothrow) GCEvent;
 
     if (!WaitForGCEvent)
     {
@@ -33887,7 +33887,7 @@ bool GCHeap::IsHeapPointer (void* vpObject, bool small_heap_only)
     STATIC_CONTRACT_SO_TOLERANT;
 
     // removed STATIC_CONTRACT_CAN_TAKE_LOCK here because find_segment 
-    // no longer calls CLREvent::Wait which eventually takes a lock.
+    // no longer calls GCEvent::Wait which eventually takes a lock.
 
     uint8_t* object = (uint8_t*) vpObject;
 #ifndef FEATURE_BASICFREEZE
index 889f940..0404058 100644 (file)
@@ -428,9 +428,14 @@ void GCHeap::SetGCInProgress(bool fInProgress)
     GcInProgress = fInProgress;
 }
 
-CLREvent * GCHeap::GetWaitForGCEvent()
+void GCHeap::SetWaitForGCEvent()
 {
-    return WaitForGCEvent;
+    WaitForGCEvent->Set();
+}
+
+void GCHeap::ResetWaitForGCEvent()
+{
+    WaitForGCEvent->Reset();
 }
 
 void GCHeap::WaitUntilConcurrentGCComplete()
@@ -520,7 +525,7 @@ void gc_heap::fire_etw_pin_object_event (uint8_t* object, uint8_t** ppObject)
 }
 #endif // FEATURE_EVENT_TRACE
 
-uint32_t gc_heap::user_thread_wait (CLREvent *event, BOOL no_mode_change, int time_out_ms)
+uint32_t gc_heap::user_thread_wait (GCEvent *event, BOOL no_mode_change, int time_out_ms)
 {
     Thread* pCurThread = NULL;
     bool mode = false;
index 2a51d47..8ac16c5 100644 (file)
@@ -6,8 +6,6 @@
 #ifndef GCIMPL_H_
 #define GCIMPL_H_
 
-#define CLREvent CLREventStatic
-
 #ifdef SERVER_GC
 #define MULTIPLE_HEAPS 1
 #endif  // SERVER_GC
@@ -93,7 +91,8 @@ public:
 
     bool RuntimeStructuresValid();
 
-    CLREvent * GetWaitForGCEvent();
+    void SetWaitForGCEvent();
+    void ResetWaitForGCEvent();
 
     HRESULT Initialize ();
 
@@ -242,7 +241,7 @@ public:     // FIX
     void TemporaryDisableConcurrentGC();
     bool IsConcurrentGCEnabled();
 
-    PER_HEAP_ISOLATED   CLREvent *WaitForGCEvent;     // used for syncing w/GC
+    PER_HEAP_ISOLATED   GCEvent *WaitForGCEvent;     // used for syncing w/GC
 
     PER_HEAP_ISOLATED    CFinalize* m_Finalize;
 
index cac2ba7..4ebb5e1 100644 (file)
@@ -687,9 +687,11 @@ public:
     // background GC as the BGC threads also need to walk LOH.
     virtual void PublishObject(uint8_t* obj) = 0;
 
-    // Gets the event that suspended threads will use to wait for the
-    // end of a GC.
-    virtual CLREventStatic* GetWaitForGCEvent() = 0;
+    // Signals the WaitForGCEvent event, indicating that a GC has completed.
+    virtual void SetWaitForGCEvent() = 0;
+
+    // Resets the state of the WaitForGCEvent back to an unsignalled state.
+    virtual void ResetWaitForGCEvent() = 0;
 
     /*
     ===========================================================================
index 108045c..a2ec64b 100644 (file)
@@ -197,8 +197,6 @@ void GCLogConfig (const char *fmt, ... );
 
 //Please leave these definitions intact.
 
-#define CLREvent CLREventStatic
-
 // hosted api
 #ifdef memcpy
 #undef memcpy
@@ -2766,7 +2764,7 @@ public:
     BOOL     dont_restart_ee_p;
 
     PER_HEAP_ISOLATED
-    CLREvent bgc_start_event;
+    GCEvent bgc_start_event;
 #endif //BACKGROUND_GC
 
     // The variables in this block are known to the DAC and must come first
@@ -2833,9 +2831,9 @@ public:
 
     PER_HEAP
 #ifndef MULTIPLE_HEAPS
-    CLREvent gc_done_event;
+    GCEvent gc_done_event;
 #else // MULTIPLE_HEAPS
-    CLREvent gc_done_event;
+    GCEvent gc_done_event;
 #endif // MULTIPLE_HEAPS
 
     PER_HEAP
@@ -2890,10 +2888,10 @@ public:
     // notification feature which is only enabled if concurrent
     // GC is disabled.
     PER_HEAP_ISOLATED
-    CLREvent full_gc_approach_event;
+    GCEvent full_gc_approach_event;
 
     PER_HEAP_ISOLATED
-    CLREvent full_gc_end_event;
+    GCEvent full_gc_end_event;
 
     // Full GC Notification percentages.
     PER_HEAP_ISOLATED
@@ -2913,9 +2911,9 @@ public:
     PER_HEAP
     size_t fgn_last_alloc;
 
-    static uint32_t user_thread_wait (CLREvent *event, BOOL no_mode_change, int time_out_ms=INFINITE);
+    static uint32_t user_thread_wait (GCEvent *event, BOOL no_mode_change, int time_out_ms=INFINITE);
 
-    static wait_full_gc_status full_gc_wait (CLREvent *event, int time_out_ms);
+    static wait_full_gc_status full_gc_wait (GCEvent *event, int time_out_ms);
 
     PER_HEAP
     uint8_t* demotion_low;
@@ -2943,10 +2941,10 @@ public:
     bool gc_thread_no_affinitize_p;
 
     PER_HEAP_ISOLATED
-    CLREvent gc_start_event;
+    GCEvent gc_start_event;
 
     PER_HEAP_ISOLATED
-    CLREvent ee_suspend_event;
+    GCEvent ee_suspend_event;
 
     PER_HEAP
     heap_segment* new_heap_segment;
@@ -3133,7 +3131,7 @@ protected:
     // we need to create them on the thread that called 
     // SuspendEE which is heap 0.
     PER_HEAP_ISOLATED
-    CLREvent bgc_threads_sync_event;
+    GCEvent bgc_threads_sync_event;
 
     PER_HEAP
     Thread* bgc_thread;
@@ -3142,13 +3140,13 @@ protected:
     CLRCriticalSection bgc_threads_timeout_cs;
 
     PER_HEAP_ISOLATED
-    CLREvent background_gc_done_event;
+    GCEvent background_gc_done_event;
 
     PER_HEAP_ISOLATED
-    CLREvent ee_proceed_event;
+    GCEvent ee_proceed_event;
 
     PER_HEAP
-    CLREvent gc_lh_block_event;
+    GCEvent gc_lh_block_event;
 
     PER_HEAP_ISOLATED
     bool gc_can_use_concurrent;
index 3e1aa5a..1025810 100644 (file)
@@ -6,6 +6,7 @@ include(configure.cmake)
 
 set(GC_PAL_SOURCES
     gcenv.unix.cpp
+    events.cpp
     cgroup.cpp)
 
 add_library(gc_unix STATIC ${GC_PAL_SOURCES} ${VERSION_FILE_PATH})
index 7578c74..21980a7 100644 (file)
@@ -10,5 +10,7 @@
 #cmakedefine01 HAVE_PTHREAD_THREADID_NP
 #cmakedefine01 HAVE_PTHREAD_GETTHREADID_NP
 #cmakedefine01 HAVE_SCHED_GETCPU
+#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK
+#cmakedefine01 HAVE_MACH_ABSOLUTE_TIME
 
-#endif // __CONFIG_H__
\ No newline at end of file
+#endif // __CONFIG_H__
index 6e1e8fe..5f2bdbd 100644 (file)
@@ -37,4 +37,19 @@ check_cxx_source_runs("
     }
     " HAVE_SCHED_GETCPU)
 
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
\ No newline at end of file
+check_library_exists(pthread pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK)
+
+check_cxx_source_runs("
+    #include <stdlib.h>
+    #include <mach/mach_time.h>
+    int main()
+    {
+        int ret;
+        mach_timebase_info_data_t timebaseInfo;
+        ret = mach_timebase_info(&timebaseInfo);
+        mach_absolute_time();
+        exit(ret);
+    }
+    " HAVE_MACH_ABSOLUTE_TIME)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
diff --git a/src/coreclr/src/gc/unix/events.cpp b/src/coreclr/src/gc/unix/events.cpp
new file mode 100644 (file)
index 0000000..f51eae8
--- /dev/null
@@ -0,0 +1,329 @@
+// 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 <cstdint>
+#include <cstddef>
+#include <cassert>
+#include <memory>
+#include <mutex>
+#include <pthread.h>
+#include <errno.h>
+#include "config.h"
+
+#ifndef __out_z
+#define __out_z
+#endif // __out_z
+
+#include "gcenv.structs.h"
+#include "gcenv.base.h"
+#include "gcenv.os.h"
+#include "globals.h"
+
+#if HAVE_MACH_ABSOLUTE_TIME
+mach_timebase_info_data_t g_TimebaseInfo;
+#endif // MACH_ABSOLUTE_TIME
+
+namespace
+{
+
+#if HAVE_PTHREAD_CONDATTR_SETCLOCK
+void TimeSpecAdd(timespec* time, uint32_t milliseconds)
+{
+    uint64_t nsec = time->tv_nsec + (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds;
+    if (nsec >= tccSecondsToNanoSeconds)
+    {
+        time->tv_sec += nsec / tccSecondsToNanoSeconds;
+        nsec %= tccSecondsToNanoSeconds;
+    }
+
+    time->tv_nsec = nsec;
+}
+#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK
+
+#if HAVE_MACH_ABSOLUTE_TIME
+// Convert nanoseconds to the timespec structure
+// Parameters:
+//  nanoseconds - time in nanoseconds to convert
+//  t           - the target timespec structure
+void NanosecondsToTimeSpec(uint64_t nanoseconds, timespec* t)
+{
+    t->tv_sec = nanoseconds / tccSecondsToNanoSeconds;
+    t->tv_nsec = nanoseconds % tccSecondsToNanoSeconds;
+}
+#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK
+
+} // anonymous namespace
+
+class GCEvent::Impl
+{
+    pthread_cond_t m_condition;
+    pthread_mutex_t m_mutex;
+    bool m_manualReset;
+    bool m_state;
+    bool m_isValid;
+
+public:
+
+    Impl(bool manualReset, bool initialState)
+    : m_manualReset(manualReset),
+      m_state(initialState),
+      m_isValid(false)
+    {
+    }
+
+    bool Initialize()
+    {
+        pthread_condattr_t attrs;
+        int st = pthread_condattr_init(&attrs);
+        if (st != 0)
+        {
+            assert(!"Failed to initialize UnixEvent condition attribute");
+            return false;
+        }
+
+        // TODO(segilles) implement this for CoreCLR
+        //PthreadCondAttrHolder attrsHolder(&attrs);
+
+#if HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_MACH_ABSOLUTE_TIME
+        // Ensure that the pthread_cond_timedwait will use CLOCK_MONOTONIC
+        st = pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
+        if (st != 0)
+        {
+            assert(!"Failed to set UnixEvent condition variable wait clock");
+            return false;
+        }
+#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_MACH_ABSOLUTE_TIME
+
+        st = pthread_mutex_init(&m_mutex, NULL);
+        if (st != 0)
+        {
+            assert(!"Failed to initialize UnixEvent mutex");
+            return false;
+        }
+
+        st = pthread_cond_init(&m_condition, &attrs);
+        if (st != 0)
+        {
+            assert(!"Failed to initialize UnixEvent condition variable");
+
+            st = pthread_mutex_destroy(&m_mutex);
+            assert(st == 0 && "Failed to destroy UnixEvent mutex");
+            return false;
+        }
+
+        m_isValid = true;
+
+        return true;
+    }
+
+    void CloseEvent()
+    {
+        if (m_isValid)
+        {
+            int st = pthread_mutex_destroy(&m_mutex);
+            assert(st == 0 && "Failed to destroy UnixEvent mutex");
+
+            st = pthread_cond_destroy(&m_condition);
+            assert(st == 0 && "Failed to destroy UnixEvent condition variable");
+        }
+    }
+
+    uint32_t Wait(uint32_t milliseconds, bool alertable)
+    {
+        UNREFERENCED_PARAMETER(alertable);
+
+        timespec endTime;
+#if HAVE_MACH_ABSOLUTE_TIME
+        uint64_t endMachTime;
+        if (milliseconds != INFINITE)
+        {
+            uint64_t nanoseconds = (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds;
+            NanosecondsToTimeSpec(nanoseconds, &endTime);
+            endMachTime = mach_absolute_time() + nanoseconds * g_TimebaseInfo.denom / g_TimebaseInfo.numer;
+        }
+#elif HAVE_PTHREAD_CONDATTR_SETCLOCK
+        if (milliseconds != INFINITE)
+        {
+            clock_gettime(CLOCK_MONOTONIC, &endTime);
+            TimeSpecAdd(&endTime, milliseconds);
+        }
+#else
+#error Don't know how to perfom timed wait on this platform
+#endif
+
+        int st = 0;
+
+        pthread_mutex_lock(&m_mutex);
+        while (!m_state)
+        {
+            if (milliseconds == INFINITE)
+            {
+                st = pthread_cond_wait(&m_condition, &m_mutex);
+            }
+            else
+            {
+#if HAVE_MACH_ABSOLUTE_TIME
+                // Since OSX doesn't support CLOCK_MONOTONIC, we use relative variant of the 
+                // timed wait and we need to handle spurious wakeups properly.
+                st = pthread_cond_timedwait_relative_np(&m_condition, &m_mutex, &endTime);
+                if ((st == 0) && !m_state)
+                {
+                    uint64_t machTime = mach_absolute_time();
+                    if (machTime < endMachTime)
+                    {
+                        // The wake up was spurious, recalculate the relative endTime
+                        uint64_t remainingNanoseconds = (endMachTime - machTime) * g_TimebaseInfo.numer / g_TimebaseInfo.denom;
+                        NanosecondsToTimeSpec(remainingNanoseconds, &endTime);
+                    }
+                    else
+                    {
+                        // Although the timed wait didn't report a timeout, time calculated from the
+                        // mach time shows we have already reached the end time. It can happen if
+                        // the wait was spuriously woken up right before the timeout.
+                        st = ETIMEDOUT;
+                    }
+                }
+#else // HAVE_MACH_ABSOLUTE_TIME
+                st = pthread_cond_timedwait(&m_condition, &m_mutex, &endTime);
+#endif // HAVE_MACH_ABSOLUTE_TIME
+                // Verify that if the wait timed out, the event was not set
+                assert((st != ETIMEDOUT) || !m_state);
+            }
+
+            if (st != 0)
+            {
+                // wait failed or timed out
+                break;
+            }
+        }
+
+        if ((st == 0) && !m_manualReset)
+        {
+            // Clear the state for auto-reset events so that only one waiter gets released
+            m_state = false;
+        }
+
+        pthread_mutex_unlock(&m_mutex);
+
+        uint32_t waitStatus;
+
+        if (st == 0)
+        {
+            waitStatus = WAIT_OBJECT_0;
+        }
+        else if (st == ETIMEDOUT)
+        {
+            waitStatus = WAIT_TIMEOUT;
+        }
+        else
+        {
+            waitStatus = WAIT_FAILED;
+        }
+
+        return waitStatus;
+    }
+
+    void Set()
+    {
+        pthread_mutex_lock(&m_mutex);
+        m_state = true;
+        pthread_mutex_unlock(&m_mutex);
+
+        // Unblock all threads waiting for the condition variable
+        pthread_cond_broadcast(&m_condition);
+    }
+
+    void Reset()
+    {
+        pthread_mutex_lock(&m_mutex);
+        m_state = false;
+        pthread_mutex_unlock(&m_mutex);
+    }
+};
+
+GCEvent::GCEvent()
+  : m_impl(nullptr)
+{
+}
+
+GCEvent::~GCEvent()
+{
+    delete m_impl;
+    m_impl = nullptr;
+}
+
+void GCEvent::CloseEvent()
+{
+    assert(m_impl != nullptr);
+    m_impl->CloseEvent();
+}
+
+void GCEvent::Set()
+{
+    assert(m_impl != nullptr);
+    m_impl->Set();
+}
+
+void GCEvent::Reset()
+{
+    assert(m_impl != nullptr);
+    m_impl->Reset();
+}
+
+uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
+{
+    assert(m_impl != nullptr);
+    return m_impl->Wait(timeout, alertable);
+}
+
+bool GCEvent::CreateAutoEventNoThrow(bool initialState)
+{
+    // This implementation of GCEvent makes no distinction between
+    // host-aware and non-host-aware events (since there will be no host).
+    return CreateOSAutoEventNoThrow(initialState);
+}
+
+bool GCEvent::CreateManualEventNoThrow(bool initialState)
+{
+    // This implementation of GCEvent makes no distinction between
+    // host-aware and non-host-aware events (since there will be no host).
+    return CreateOSManualEventNoThrow(initialState);
+}
+
+bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
+{
+    assert(m_impl == nullptr);
+    std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl(false, initialState));
+    if (!event)
+    {
+        return false;
+    }
+
+    if (!event->Initialize())
+    {
+        return false;
+    }
+
+    m_impl = event.release();
+    return true;
+}
+
+bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
+{
+    assert(m_impl == nullptr);
+    std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl(true, initialState));
+    if (!event)
+    {
+        return false;
+    }
+
+    if (!event->Initialize())
+    {
+        return false;
+    }
+
+    m_impl = event.release();
+    return true;
+}
+
index cad83a3..5fc63f4 100644 (file)
@@ -36,13 +36,13 @@ static_assert(sizeof(uint64_t) == 8, "unsigned long isn't 8 bytes");
  #error "A GC-private implementation of GCToOSInterface should only be used with FEATURE_STANDALONE_GC"
 #endif // FEATURE_STANDALONE_GC
 
-#ifdef HAVE_SYS_TIME_H
+#if HAVE_SYS_TIME_H
  #include <sys/time.h>
 #else
  #error "sys/time.h required by GC PAL for the time being"
 #endif // HAVE_SYS_TIME_
 
-#ifdef HAVE_SYS_MMAN_H
+#if HAVE_SYS_MMAN_H
  #include <sys/mman.h>
 #else
  #error "sys/mman.h required by GC PAL"
@@ -56,18 +56,7 @@ static_assert(sizeof(uint64_t) == 8, "unsigned long isn't 8 bytes");
 #include <sched.h> // sched_yield
 #include <errno.h>
 #include <unistd.h> // sysconf
-
-// The number of milliseconds in a second.
-static const int tccSecondsToMilliSeconds = 1000;
-
-// The number of microseconds in a second.
-static const int tccSecondsToMicroSeconds = 1000000;
-
-// The number of microseconds in a millisecond.
-static const int tccMilliSecondsToMicroSeconds = 1000;
-
-// The number of nanoseconds in a millisecond.
-static const int tccMilliSecondsToNanoSeconds = 1000000;
+#include "globals.h"
 
 // The cachced number of logical CPUs observed.
 static uint32_t g_logicalCpuCount = 0;
@@ -117,6 +106,14 @@ bool GCToOSInterface::Initialize()
         return false;
     }
 
+#if HAVE_MACH_ABSOLUTE_TIME
+    kern_return_t machRet;
+    if ((machRet = mach_timebase_info(&g_TimebaseInfo)) != KERN_SUCCESS)
+    {
+        return false;
+    }
+#endif // HAVE_MACH_ABSOLUTE_TIME
+
     return true;
 }
 
diff --git a/src/coreclr/src/gc/unix/globals.h b/src/coreclr/src/gc/unix/globals.h
new file mode 100644 (file)
index 0000000..bc3dc49
--- /dev/null
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef __GLOBALS_H__
+#define __GLOBALS_H__
+
+#if HAVE_MACH_ABSOLUTE_TIME
+#include <mach/mach_time.h>
+#endif // HAVE_MACH_ABSOLUTE_TIME
+
+const int tccSecondsToMilliSeconds = 1000;
+
+// The number of microseconds in a second.
+const int tccSecondsToMicroSeconds = 1000000;
+
+// The number of nanoseconds in a second.
+const int tccSecondsToNanoSeconds = 1000000000;
+
+// The number of microseconds in a millisecond.
+const int tccMilliSecondsToMicroSeconds = 1000;
+
+// The number of nanoseconds in a millisecond.
+const int tccMilliSecondsToNanoSeconds = 1000000;
+
+#if HAVE_MACH_ABSOLUTE_TIME
+extern mach_timebase_info_data_t g_TimebaseInfo;
+#endif // HAVE_MACH_ABSOLUTE_TIME
+
+#endif // __GLOBALS_H__
index 30232bf..0f3fd71 100644 (file)
@@ -626,3 +626,151 @@ void CLRCriticalSection::Leave()
 {
     ::LeaveCriticalSection(&m_cs);
 }
+
+// WindowsEvent is an implementation of GCEvent that forwards
+// directly to Win32 APIs.
+class GCEvent::Impl
+{
+private:
+    HANDLE m_hEvent;
+
+public:
+    Impl() : m_hEvent(INVALID_HANDLE_VALUE) {}
+
+    bool IsValid() const
+    {
+        return m_hEvent != INVALID_HANDLE_VALUE;
+    }
+
+    void Set()
+    {
+        assert(IsValid());
+        BOOL result = SetEvent(m_hEvent);
+        assert(result && "SetEvent failed");
+    }
+
+    void Reset()
+    {
+        assert(IsValid());
+        BOOL result = ResetEvent(m_hEvent);
+        assert(result && "ResetEvent failed");
+    }
+
+    uint32_t Wait(uint32_t timeout, bool alertable)
+    {
+        UNREFERENCED_PARAMETER(alertable);
+        assert(IsValid());
+
+        return WaitForSingleObject(m_hEvent, timeout);
+    }
+
+    void CloseEvent()
+    {
+        assert(IsValid());
+        BOOL result = CloseHandle(m_hEvent);
+        assert(result && "CloseHandle failed");
+        m_hEvent = INVALID_HANDLE_VALUE;
+    }
+
+    bool CreateAutoEvent(bool initialState)
+    {
+        m_hEvent = CreateEvent(nullptr, false, initialState, nullptr);
+        return IsValid();
+    }
+
+    bool CreateManualEvent(bool initialState)
+    {
+        m_hEvent = CreateEvent(nullptr, true, initialState, nullptr);
+        return IsValid();
+    }
+};
+
+GCEvent::GCEvent()
+  : m_impl(nullptr)
+{
+}
+
+GCEvent::~GCEvent()
+{
+    delete m_impl;
+    m_impl = nullptr;
+}
+
+void GCEvent::CloseEvent()
+{
+    assert(m_impl != nullptr);
+    m_impl->CloseEvent();
+}
+
+void GCEvent::Set()
+{
+    assert(m_impl != nullptr);
+    m_impl->Set();
+}
+
+void GCEvent::Reset()
+{
+    assert(m_impl != nullptr);
+    m_impl->Reset();
+}
+
+uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
+{
+    assert(m_impl != nullptr);
+    return m_impl->Wait(timeout, alertable);
+}
+
+bool GCEvent::CreateAutoEventNoThrow(bool initialState)
+{
+    // [DESKTOP TODO] The difference between events and OS events is
+    // whether or not the hosting API is made aware of them. When (if)
+    // we implement hosting support for Local GC, we will need to be
+    // aware of the host here.
+    return CreateOSAutoEventNoThrow(initialState);
+}
+
+bool GCEvent::CreateManualEventNoThrow(bool initialState)
+{
+    // [DESKTOP TODO] The difference between events and OS events is
+    // whether or not the hosting API is made aware of them. When (if)
+    // we implement hosting support for Local GC, we will need to be
+    // aware of the host here.
+    return CreateOSManualEventNoThrow(initialState);
+}
+
+bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
+{
+    assert(m_impl == nullptr);
+    std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl());
+    if (!event)
+    {
+        return false;
+    }
+
+    if (!event->CreateAutoEvent(initialState))
+    {
+        return false;
+    }
+
+    m_impl = event.release();
+    return true;
+}
+
+bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
+{
+    assert(m_impl == nullptr);
+    std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl());
+    if (!event)
+    {
+        return false;
+    }
+
+    if (!event->CreateManualEvent(initialState))
+    {
+        return false;
+    }
+
+    m_impl = event.release();
+    return true;
+}
+
index 6c08558..d519c3c 100644 (file)
@@ -700,3 +700,214 @@ void CLRCriticalSection::Leave()
     WRAPPER_NO_CONTRACT;
     UnsafeLeaveCriticalSection(&m_cs);
 }
+
+// An implementatino of GCEvent that delegates to
+// a CLREvent, which in turn delegates to the PAL. This event
+// is also host-aware.
+class GCEvent::Impl
+{
+private:
+    CLREvent m_event;
+
+public:
+    Impl() = default;
+
+    bool IsValid()
+    {
+        WRAPPER_NO_CONTRACT;
+
+        return !!m_event.IsValid();
+    }
+
+    void CloseEvent()
+    {
+        WRAPPER_NO_CONTRACT;
+
+        assert(m_event.IsValid());
+        m_event.CloseEvent();
+    }
+
+    void Set()
+    {
+        WRAPPER_NO_CONTRACT;
+
+        assert(m_event.IsValid());
+        m_event.Set();
+    }
+
+    void Reset()
+    {
+        WRAPPER_NO_CONTRACT;
+
+        assert(m_event.IsValid());
+        m_event.Reset();
+    }
+
+    uint32_t Wait(uint32_t timeout, bool alertable)
+    {
+        WRAPPER_NO_CONTRACT;
+
+        assert(m_event.IsValid());
+        return m_event.Wait(timeout, alertable);
+    }
+
+    bool CreateAutoEvent(bool initialState)
+    {
+        CONTRACTL {
+            NOTHROW;
+            GC_NOTRIGGER;
+        } CONTRACTL_END;
+
+        return !!m_event.CreateAutoEventNoThrow(initialState);
+    }
+
+    bool CreateManualEvent(bool initialState)
+    {
+        CONTRACTL {
+            NOTHROW;
+            GC_NOTRIGGER;
+        } CONTRACTL_END;
+
+        return !!m_event.CreateManualEventNoThrow(initialState);
+    }
+
+    bool CreateOSAutoEvent(bool initialState)
+    {
+        CONTRACTL {
+            NOTHROW;
+            GC_NOTRIGGER;
+        } CONTRACTL_END;
+
+        return !!m_event.CreateOSAutoEventNoThrow(initialState);
+    }
+
+    bool CreateOSManualEvent(bool initialState)
+    {
+        CONTRACTL {
+            NOTHROW;
+            GC_NOTRIGGER;
+        } CONTRACTL_END;
+
+        return !!m_event.CreateOSManualEventNoThrow(initialState);
+    }
+};
+
+GCEvent::GCEvent()
+  : m_impl(nullptr)
+{
+}
+
+GCEvent::~GCEvent()
+{
+    delete m_impl;
+    m_impl = nullptr;
+}
+
+void GCEvent::CloseEvent()
+{
+    WRAPPER_NO_CONTRACT;
+
+    assert(m_impl != nullptr);
+    m_impl->CloseEvent();
+}
+
+void GCEvent::Set()
+{
+    WRAPPER_NO_CONTRACT;
+
+    assert(m_impl != nullptr);
+    m_impl->Set();
+}
+
+void GCEvent::Reset()
+{
+    WRAPPER_NO_CONTRACT;
+
+    assert(m_impl != nullptr);
+    m_impl->Reset();
+}
+
+uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
+{
+    WRAPPER_NO_CONTRACT;
+
+    assert(m_impl != nullptr);
+    return m_impl->Wait(timeout, alertable);
+}
+
+bool GCEvent::CreateManualEventNoThrow(bool initialState)
+{
+    CONTRACTL {
+      NOTHROW;
+      GC_NOTRIGGER;
+    } CONTRACTL_END;
+
+    assert(m_impl == nullptr);
+    NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
+    if (!event)
+    {
+        return false;
+    }
+
+    event->CreateManualEvent(initialState);
+    m_impl = event.Extract();
+    return true;
+}
+
+bool GCEvent::CreateAutoEventNoThrow(bool initialState)
+{
+    CONTRACTL {
+      NOTHROW;
+      GC_NOTRIGGER;
+    } CONTRACTL_END;
+
+    assert(m_impl == nullptr);
+    NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
+    if (!event)
+    {
+        return false;
+    }
+
+    event->CreateAutoEvent(initialState);
+    m_impl = event.Extract();
+    return IsValid();
+}
+
+bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
+{
+    CONTRACTL {
+      NOTHROW;
+      GC_NOTRIGGER;
+    } CONTRACTL_END;
+
+    assert(m_impl == nullptr);
+    NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
+    if (!event)
+    {
+        return false;
+    }
+
+    event->CreateOSAutoEvent(initialState);
+    m_impl = event.Extract();
+    return IsValid();
+}
+
+bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
+{
+    CONTRACTL {
+      NOTHROW;
+      GC_NOTRIGGER;
+    } CONTRACTL_END;
+
+    assert(m_impl == nullptr);
+    NewHolder<GCEvent::Impl> event = new (nothrow) GCEvent::Impl();
+    if (!event)
+    {
+        return false;
+    }
+
+    event->CreateOSManualEvent(initialState);
+    m_impl = event.Extract();
+    return IsValid();
+}
+
index ab1f2bb..6549594 100644 (file)
@@ -7181,7 +7181,7 @@ void ThreadSuspend::RestartEE(BOOL bFinishedGC, BOOL SuspendSucceded)
     // 
     // Any threads that are waiting in WaitUntilGCComplete will continue now.
     //
-    GCHeapUtilities::GetGCHeap()->GetWaitForGCEvent()->Set();
+    GCHeapUtilities::GetGCHeap()->SetWaitForGCEvent();
     _ASSERTE(IsGCSpecialThread() || ThreadStore::HoldingThreadStore());
 
     ResumeRuntime(bFinishedGC, SuspendSucceded);
@@ -7307,7 +7307,7 @@ retry_for_debugger:
         //
         // First, we reset the event that we're about to tell other threads to wait for.
         //
-        GCHeapUtilities::GetGCHeap()->GetWaitForGCEvent()->Reset();
+        GCHeapUtilities::GetGCHeap()->ResetWaitForGCEvent();
 
         //
         // Remember that we're the one doing the GC.  Actually, maybe we're not doing a GC -