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