Remove destructor from GCEvent and instead rely on the OS to clean up (#11132)
[platform/upstream/coreclr.git] / src / gc / unix / events.cpp
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 #include <cstdint>
6 #include <cstddef>
7 #include <cassert>
8 #include <memory>
9 #include <mutex>
10 #include <pthread.h>
11 #include <errno.h>
12 #include "config.h"
13
14 #ifndef __out_z
15 #define __out_z
16 #endif // __out_z
17
18 #include "gcenv.structs.h"
19 #include "gcenv.base.h"
20 #include "gcenv.os.h"
21 #include "globals.h"
22
23 #if HAVE_MACH_ABSOLUTE_TIME
24 mach_timebase_info_data_t g_TimebaseInfo;
25 #endif // MACH_ABSOLUTE_TIME
26
27 namespace
28 {
29
30 #if HAVE_PTHREAD_CONDATTR_SETCLOCK
31 void TimeSpecAdd(timespec* time, uint32_t milliseconds)
32 {
33     uint64_t nsec = time->tv_nsec + (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds;
34     if (nsec >= tccSecondsToNanoSeconds)
35     {
36         time->tv_sec += nsec / tccSecondsToNanoSeconds;
37         nsec %= tccSecondsToNanoSeconds;
38     }
39
40     time->tv_nsec = nsec;
41 }
42 #endif // HAVE_PTHREAD_CONDATTR_SETCLOCK
43
44 #if HAVE_MACH_ABSOLUTE_TIME
45 // Convert nanoseconds to the timespec structure
46 // Parameters:
47 //  nanoseconds - time in nanoseconds to convert
48 //  t           - the target timespec structure
49 void NanosecondsToTimeSpec(uint64_t nanoseconds, timespec* t)
50 {
51     t->tv_sec = nanoseconds / tccSecondsToNanoSeconds;
52     t->tv_nsec = nanoseconds % tccSecondsToNanoSeconds;
53 }
54 #endif // HAVE_PTHREAD_CONDATTR_SETCLOCK
55
56 } // anonymous namespace
57
58 class GCEvent::Impl
59 {
60     pthread_cond_t m_condition;
61     pthread_mutex_t m_mutex;
62     bool m_manualReset;
63     bool m_state;
64     bool m_isValid;
65
66 public:
67
68     Impl(bool manualReset, bool initialState)
69     : m_manualReset(manualReset),
70       m_state(initialState),
71       m_isValid(false)
72     {
73     }
74
75     bool Initialize()
76     {
77         pthread_condattr_t attrs;
78         int st = pthread_condattr_init(&attrs);
79         if (st != 0)
80         {
81             assert(!"Failed to initialize UnixEvent condition attribute");
82             return false;
83         }
84
85         // TODO(segilles) implement this for CoreCLR
86         //PthreadCondAttrHolder attrsHolder(&attrs);
87
88 #if HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_MACH_ABSOLUTE_TIME
89         // Ensure that the pthread_cond_timedwait will use CLOCK_MONOTONIC
90         st = pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
91         if (st != 0)
92         {
93             assert(!"Failed to set UnixEvent condition variable wait clock");
94             return false;
95         }
96 #endif // HAVE_PTHREAD_CONDATTR_SETCLOCK && !HAVE_MACH_ABSOLUTE_TIME
97
98         st = pthread_mutex_init(&m_mutex, NULL);
99         if (st != 0)
100         {
101             assert(!"Failed to initialize UnixEvent mutex");
102             return false;
103         }
104
105         st = pthread_cond_init(&m_condition, &attrs);
106         if (st != 0)
107         {
108             assert(!"Failed to initialize UnixEvent condition variable");
109
110             st = pthread_mutex_destroy(&m_mutex);
111             assert(st == 0 && "Failed to destroy UnixEvent mutex");
112             return false;
113         }
114
115         m_isValid = true;
116
117         return true;
118     }
119
120     void CloseEvent()
121     {
122         if (m_isValid)
123         {
124             int st = pthread_mutex_destroy(&m_mutex);
125             assert(st == 0 && "Failed to destroy UnixEvent mutex");
126
127             st = pthread_cond_destroy(&m_condition);
128             assert(st == 0 && "Failed to destroy UnixEvent condition variable");
129         }
130     }
131
132     uint32_t Wait(uint32_t milliseconds, bool alertable)
133     {
134         UNREFERENCED_PARAMETER(alertable);
135
136         timespec endTime;
137 #if HAVE_MACH_ABSOLUTE_TIME
138         uint64_t endMachTime;
139         if (milliseconds != INFINITE)
140         {
141             uint64_t nanoseconds = (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds;
142             NanosecondsToTimeSpec(nanoseconds, &endTime);
143             endMachTime = mach_absolute_time() + nanoseconds * g_TimebaseInfo.denom / g_TimebaseInfo.numer;
144         }
145 #elif HAVE_PTHREAD_CONDATTR_SETCLOCK
146         if (milliseconds != INFINITE)
147         {
148             clock_gettime(CLOCK_MONOTONIC, &endTime);
149             TimeSpecAdd(&endTime, milliseconds);
150         }
151 #else
152 #error Don't know how to perfom timed wait on this platform
153 #endif
154
155         int st = 0;
156
157         pthread_mutex_lock(&m_mutex);
158         while (!m_state)
159         {
160             if (milliseconds == INFINITE)
161             {
162                 st = pthread_cond_wait(&m_condition, &m_mutex);
163             }
164             else
165             {
166 #if HAVE_MACH_ABSOLUTE_TIME
167                 // Since OSX doesn't support CLOCK_MONOTONIC, we use relative variant of the 
168                 // timed wait and we need to handle spurious wakeups properly.
169                 st = pthread_cond_timedwait_relative_np(&m_condition, &m_mutex, &endTime);
170                 if ((st == 0) && !m_state)
171                 {
172                     uint64_t machTime = mach_absolute_time();
173                     if (machTime < endMachTime)
174                     {
175                         // The wake up was spurious, recalculate the relative endTime
176                         uint64_t remainingNanoseconds = (endMachTime - machTime) * g_TimebaseInfo.numer / g_TimebaseInfo.denom;
177                         NanosecondsToTimeSpec(remainingNanoseconds, &endTime);
178                     }
179                     else
180                     {
181                         // Although the timed wait didn't report a timeout, time calculated from the
182                         // mach time shows we have already reached the end time. It can happen if
183                         // the wait was spuriously woken up right before the timeout.
184                         st = ETIMEDOUT;
185                     }
186                 }
187 #else // HAVE_MACH_ABSOLUTE_TIME
188                 st = pthread_cond_timedwait(&m_condition, &m_mutex, &endTime);
189 #endif // HAVE_MACH_ABSOLUTE_TIME
190                 // Verify that if the wait timed out, the event was not set
191                 assert((st != ETIMEDOUT) || !m_state);
192             }
193
194             if (st != 0)
195             {
196                 // wait failed or timed out
197                 break;
198             }
199         }
200
201         if ((st == 0) && !m_manualReset)
202         {
203             // Clear the state for auto-reset events so that only one waiter gets released
204             m_state = false;
205         }
206
207         pthread_mutex_unlock(&m_mutex);
208
209         uint32_t waitStatus;
210
211         if (st == 0)
212         {
213             waitStatus = WAIT_OBJECT_0;
214         }
215         else if (st == ETIMEDOUT)
216         {
217             waitStatus = WAIT_TIMEOUT;
218         }
219         else
220         {
221             waitStatus = WAIT_FAILED;
222         }
223
224         return waitStatus;
225     }
226
227     void Set()
228     {
229         pthread_mutex_lock(&m_mutex);
230         m_state = true;
231         pthread_mutex_unlock(&m_mutex);
232
233         // Unblock all threads waiting for the condition variable
234         pthread_cond_broadcast(&m_condition);
235     }
236
237     void Reset()
238     {
239         pthread_mutex_lock(&m_mutex);
240         m_state = false;
241         pthread_mutex_unlock(&m_mutex);
242     }
243 };
244
245 GCEvent::GCEvent()
246   : m_impl(nullptr)
247 {
248 }
249
250 void GCEvent::CloseEvent()
251 {
252     assert(m_impl != nullptr);
253     m_impl->CloseEvent();
254 }
255
256 void GCEvent::Set()
257 {
258     assert(m_impl != nullptr);
259     m_impl->Set();
260 }
261
262 void GCEvent::Reset()
263 {
264     assert(m_impl != nullptr);
265     m_impl->Reset();
266 }
267
268 uint32_t GCEvent::Wait(uint32_t timeout, bool alertable)
269 {
270     assert(m_impl != nullptr);
271     return m_impl->Wait(timeout, alertable);
272 }
273
274 bool GCEvent::CreateAutoEventNoThrow(bool initialState)
275 {
276     // This implementation of GCEvent makes no distinction between
277     // host-aware and non-host-aware events (since there will be no host).
278     return CreateOSAutoEventNoThrow(initialState);
279 }
280
281 bool GCEvent::CreateManualEventNoThrow(bool initialState)
282 {
283     // This implementation of GCEvent makes no distinction between
284     // host-aware and non-host-aware events (since there will be no host).
285     return CreateOSManualEventNoThrow(initialState);
286 }
287
288 bool GCEvent::CreateOSAutoEventNoThrow(bool initialState)
289 {
290     assert(m_impl == nullptr);
291     std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl(false, initialState));
292     if (!event)
293     {
294         return false;
295     }
296
297     if (!event->Initialize())
298     {
299         return false;
300     }
301
302     m_impl = event.release();
303     return true;
304 }
305
306 bool GCEvent::CreateOSManualEventNoThrow(bool initialState)
307 {
308     assert(m_impl == nullptr);
309     std::unique_ptr<GCEvent::Impl> event(new (std::nothrow) GCEvent::Impl(true, initialState));
310     if (!event)
311     {
312         return false;
313     }
314
315     if (!event->Initialize())
316     {
317         return false;
318     }
319
320     m_impl = event.release();
321     return true;
322 }
323