forked from getsentry/sentry-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsentry_sync.h
More file actions
692 lines (609 loc) · 24.6 KB
/
Copy pathsentry_sync.h
File metadata and controls
692 lines (609 loc) · 24.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
#ifndef SENTRY_SYNC_H_INCLUDED
#define SENTRY_SYNC_H_INCLUDED
#include "sentry_boot.h"
#include "sentry_core.h"
#include <assert.h>
#include <stdio.h>
// This is a NOP for platforms that support static mutex initialization.
#define SENTRY__MUTEX_INIT_DYN_ONCE(Mutex) ((void)0)
#ifdef _MSC_VER
# define THREAD_FUNCTION_API __stdcall
#else
# define THREAD_FUNCTION_API
#endif
#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__)
# define UNSIGNED_MINGW unsigned
#else
# define UNSIGNED_MINGW
#endif
// pthreads use `void *` return types, whereas windows uses `DWORD`
#ifdef SENTRY_PLATFORM_WINDOWS
# define SENTRY_THREAD_FN static UNSIGNED_MINGW DWORD THREAD_FUNCTION_API
#else
# define SENTRY_THREAD_FN static void *
#endif
// define a recursive mutex for all platforms
#ifdef SENTRY_PLATFORM_WINDOWS
# if _WIN32_WINNT >= 0x0600
# include <synchapi.h>
# endif
# include <winnt.h>
# if _WIN32_WINNT < 0x0600
# define INIT_ONCE_STATIC_INIT { 0 }
typedef union {
PVOID Ptr;
} INIT_ONCE, *PINIT_ONCE;
typedef struct {
HANDLE Semaphore;
HANDLE ContinueEvent;
LONG Waiters;
LONG Target;
} CONDITION_VARIABLE_PREVISTA, *PCONDITION_VARIABLE_PREVISTA;
typedef BOOL(WINAPI *PINIT_ONCE_FN)(
PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context);
inline BOOL
InitOnceExecuteOnce(
PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, PVOID Parameter, LPVOID *Context)
{
for (;;) {
switch ((ULONG_PTR)InitOnce->Ptr) {
case 0: // not started
if (InterlockedCompareExchangePointer(
&InitOnce->Ptr, (PVOID)1, (PVOID)0)
!= 0) {
break;
}
if (InitFn(InitOnce, Parameter, Context)) {
InitOnce->Ptr = (PVOID)2;
return TRUE;
}
InitOnce->Ptr = 0;
return FALSE;
case 1: // in progress
Sleep(1);
break;
case 2: // completed
return TRUE;
default: // unexpecterd value
return FALSE;
}
}
}
inline void
InitializeConditionVariable_PREVISTA(
PCONDITION_VARIABLE_PREVISTA ConditionVariable)
{
ConditionVariable->Target = 0;
ConditionVariable->Waiters = 0;
ConditionVariable->Semaphore = CreateSemaphoreW(NULL, 0, MAXLONG, NULL);
ConditionVariable->ContinueEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
}
inline BOOL
SleepConditionVariableCS_PREVISTA(
PCONDITION_VARIABLE_PREVISTA cv, PCRITICAL_SECTION cs, DWORD timeout)
{
DWORD result = 0;
LeaveCriticalSection(cs);
InterlockedIncrement((LONG *)&cv->Waiters);
result = WaitForSingleObject(cv->Semaphore, timeout);
// send event only on target
if (InterlockedDecrement((LONG *)&cv->Waiters) == cv->Target
&& result == WAIT_OBJECT_0) {
SetEvent(cv->ContinueEvent);
}
EnterCriticalSection(cs);
return result;
}
inline void
WakeConditionVariable_PREVISTA(PCONDITION_VARIABLE_PREVISTA ConditionVariable)
{
if (!ConditionVariable) {
return;
}
if (ConditionVariable->Waiters == 0) {
return;
}
// set target for continue event, alert it on first occurance
ConditionVariable->Target = ConditionVariable->Waiters - 1;
SignalObjectAndWait(ConditionVariable->Semaphore,
ConditionVariable->ContinueEvent, INFINITE, FALSE);
}
# endif /* _WIN32_WINNT < 0x0600 */
struct sentry__winmutex_s {
INIT_ONCE init_once;
CRITICAL_SECTION critical_section;
};
static inline BOOL CALLBACK
sentry__winmutex_initonce(
PINIT_ONCE UNUSED(InitOnce), PVOID cs, PVOID *UNUSED(lpContext))
{
InitializeCriticalSection((LPCRITICAL_SECTION)cs);
return TRUE;
}
static inline void
sentry__winmutex_init(struct sentry__winmutex_s *mutex)
{
InitOnceExecuteOnce(&mutex->init_once, sentry__winmutex_initonce,
&mutex->critical_section, NULL);
}
static inline void
sentry__winmutex_lock(struct sentry__winmutex_s *mutex)
{
InitOnceExecuteOnce(&mutex->init_once, sentry__winmutex_initonce,
&mutex->critical_section, NULL);
EnterCriticalSection(&mutex->critical_section);
}
typedef HANDLE sentry_threadid_t;
typedef struct sentry__winmutex_s sentry_mutex_t;
# define SENTRY__MUTEX_INIT { INIT_ONCE_STATIC_INIT, { 0 } }
# define sentry__mutex_init(Lock) sentry__winmutex_init(Lock)
# define sentry__mutex_lock(Lock) sentry__winmutex_lock(Lock)
# define sentry__mutex_unlock(Lock) \
LeaveCriticalSection(&(Lock)->critical_section)
# define sentry__mutex_free(Lock) \
DeleteCriticalSection(&(Lock)->critical_section)
# define sentry__thread_init(ThreadId) *ThreadId = INVALID_HANDLE_VALUE
# define sentry__thread_spawn(ThreadId, Func, Data) \
(*ThreadId = CreateThread(NULL, 0, Func, Data, 0, NULL), \
*ThreadId == INVALID_HANDLE_VALUE ? 1 : 0)
# define sentry__thread_join(ThreadId) \
WaitForSingleObject(ThreadId, INFINITE)
# define sentry__thread_detach(ThreadId) (void)ThreadId
# define sentry__thread_free(ThreadId) \
do { \
if (*ThreadId != INVALID_HANDLE_VALUE) { \
CloseHandle(*ThreadId); \
} \
*ThreadId = INVALID_HANDLE_VALUE; \
} while (0)
# if _WIN32_WINNT < 0x0600
typedef CONDITION_VARIABLE_PREVISTA sentry_cond_t;
# define sentry__cond_init(CondVar) \
InitializeConditionVariable_PREVISTA(CondVar)
# define sentry__cond_wake WakeConditionVariable_PREVISTA
# define sentry__cond_wait_timeout(CondVar, Lock, Timeout) \
SleepConditionVariableCS_PREVISTA( \
CondVar, &(Lock)->critical_section, Timeout)
# else
typedef CONDITION_VARIABLE sentry_cond_t;
# define sentry__cond_init(CondVar) InitializeConditionVariable(CondVar)
# define sentry__cond_wake WakeConditionVariable
# define sentry__cond_wait_timeout(CondVar, Lock, Timeout) \
SleepConditionVariableCS( \
CondVar, &(Lock)->critical_section, Timeout)
# endif
# define sentry__cond_wait(CondVar, Lock) \
sentry__cond_wait_timeout(CondVar, Lock, INFINITE)
static inline sentry_threadid_t
sentry__current_thread(void)
{
return GetCurrentThread();
}
static inline int
sentry__threadid_equal(sentry_threadid_t a, sentry_threadid_t b)
{
return GetThreadId(a) == GetThreadId(b);
}
#else
# include <errno.h>
# include <pthread.h>
# include <sys/time.h>
/* on unix systems signal handlers can interrupt anything which means that
we're restricted in what we can do. In particular it's possible that
we would end up dead locking ourselves. While we cannot fully prevent
races we have a logic here that while the signal handler is active we're
disabling our mutexes so that our signal handler can access what
otherwise would be protected by the mutex but everyone else needs to wait
for the signal handler to finish. This is not without risk because
another thread might still access what the mutex protects.
We are thus taking care that whatever such mutexes protect will not make
us crash under concurrent modifications. The mutexes we're likely going
to hit are the options and scope lock. */
bool sentry__block_for_signal_handler(void);
/**
* Enter signal handler context. Returns the recursion depth:
* 1 = first entry, normal processing
* 2 = re-entry (crash during handling), skip hooks but try to capture
* 3+ = multiple re-entries, bail out to previous handler
*/
int sentry__enter_signal_handler(void);
void sentry__leave_signal_handler(void);
typedef pthread_t sentry_threadid_t;
typedef pthread_mutex_t sentry_mutex_t;
typedef pthread_cond_t sentry_cond_t;
# ifdef SENTRY_PLATFORM_LINUX
# ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
// In particular musl libc does not define a recursive initializer itself.
// However, we can just define our own. Following the chain of how musl
// initializes its mutex, the attributes are the first member:
// https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_mutex_init.c#n6
// https://git.musl-libc.org/cgit/musl/tree/src/internal/pthread_impl.h#n86
// https://git.musl-libc.org/cgit/musl/tree/include/alltypes.h.in#n86
# define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP \
{ \
{ \
{ \
PTHREAD_MUTEX_RECURSIVE \
} \
} \
}
# endif
# define SENTRY__MUTEX_INIT PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
# elif defined(SENTRY_PLATFORM_AIX)
// AIX lacks PTHREAD_RECURSIVE_MUTEX_INITIALIZER, though it does have at least
// PTHREAD_MUTEX_INITIALIZER. Unfortunately, it means you must call the mutex
// init function with an initialized mutexattrs set to recursive. This isn't
// workable due to all the mutexes just sitting around not initialized but
// immediately used. The fields are basically guesswork from what happens when
// you initialize the mutex "properly" but changing the bare minimum from a
// static initialization. (Don't ask me what these fields mean, the struct is
// opaquely defined as long[n] fields.)
# define SENTRY__MUTEX_INIT \
{ \
{ \
0, 0, 0, 0, /*_PTH_FLAGS_INIT64*/ 1, \
PTHREAD_MUTEX_RECURSIVE \
} \
}
# elif defined(__FreeBSD__) || defined(SENTRY_PLATFORM_NX)
// Don't define `SENTRY__MUTEX_INIT` but instead provide a new definition that
// can be used by platforms requiring dynamic recursive mutex initialization.
# define SENTRY__MUTEX_INIT_DYN(Mutex) \
static sentry_mutex_t Mutex; \
static pthread_once_t Mutex##_init_once = PTHREAD_ONCE_INIT; \
static void init_##Mutex(void) { sentry__mutex_init(&Mutex); }
# undef SENTRY__MUTEX_INIT_DYN_ONCE
// Ensures that our dynamically configured mutex is safely initialized once.
# define SENTRY__MUTEX_INIT_DYN_ONCE(Mutex) \
pthread_once(&Mutex##_init_once, init_##Mutex)
# else
# define SENTRY__MUTEX_INIT PTHREAD_RECURSIVE_MUTEX_INITIALIZER
# endif
# ifdef SENTRY__MUTEX_INIT_DYN
# define sentry__mutex_init(Mutex) \
do { \
pthread_mutexattr_t attr; \
pthread_mutexattr_init(&attr); \
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
pthread_mutex_init(Mutex, &attr); \
pthread_mutexattr_destroy(&attr); \
} while (0)
# else
# define sentry__mutex_init(Mutex) \
do { \
sentry_mutex_t tmp = SENTRY__MUTEX_INIT; \
*(Mutex) = tmp; \
} while (0)
# endif
# define sentry__mutex_lock(Mutex) \
do { \
if (sentry__block_for_signal_handler()) { \
int rv = pthread_mutex_lock(Mutex); \
(void)rv; \
assert(rv == 0); \
} \
} while (0)
# define sentry__mutex_unlock(Mutex) \
do { \
if (sentry__block_for_signal_handler()) { \
pthread_mutex_unlock(Mutex); \
} \
} while (0)
# define sentry__mutex_free(Lock) pthread_mutex_destroy(Lock)
# define sentry__cond_init(CondVar) \
do { \
sentry_cond_t tmp = PTHREAD_COND_INITIALIZER; \
*(CondVar) = tmp; \
} while (0)
# define sentry__cond_wait(Cond, Mutex) \
do { \
if (sentry__block_for_signal_handler()) { \
pthread_cond_wait(Cond, Mutex); \
} \
} while (0)
# define sentry__cond_wake pthread_cond_signal
# define sentry__thread_init(ThreadId) \
memset(ThreadId, 0, sizeof(sentry_threadid_t))
# define sentry__thread_spawn(ThreadId, Func, Data) \
(pthread_create(ThreadId, NULL, Func, Data) == 0 ? 0 : 1)
# define sentry__thread_join(ThreadId) pthread_join(ThreadId, NULL)
# define sentry__thread_detach(ThreadId) pthread_detach(ThreadId)
# define sentry__thread_free sentry__thread_init
# define sentry__threadid_equal pthread_equal
# define sentry__current_thread pthread_self
static inline int
sentry__cond_wait_timeout(
sentry_cond_t *cv, sentry_mutex_t *mutex, uint64_t msecs)
{
if (!sentry__block_for_signal_handler()) {
return 0;
}
struct timeval now;
struct timespec lock_time;
gettimeofday(&now, NULL);
lock_time.tv_sec = now.tv_sec + msecs / 1000ULL;
lock_time.tv_nsec = (now.tv_usec + 1000ULL * (msecs % 1000)) * 1000ULL;
return pthread_cond_timedwait(cv, mutex, &lock_time);
}
#endif
static inline long
sentry__atomic_fetch_and_add(volatile long *val, long diff)
{
#ifdef SENTRY_PLATFORM_WINDOWS
# if SIZEOF_LONG == 8
return InterlockedExchangeAdd64((LONG64 *)val, diff);
# else
return InterlockedExchangeAdd((LONG *)val, diff);
# endif
#else
return __atomic_fetch_add(val, diff, __ATOMIC_SEQ_CST);
#endif
}
static inline long
sentry__atomic_store(volatile long *val, long value)
{
#ifdef SENTRY_PLATFORM_WINDOWS
# if SIZEOF_LONG == 8
return InterlockedExchange64((LONG64 *)val, value);
# else
return InterlockedExchange((LONG *)val, value);
# endif
#else
return __atomic_exchange_n(val, value, __ATOMIC_SEQ_CST);
#endif
}
static inline long
sentry__atomic_fetch(volatile long *val)
{
return sentry__atomic_fetch_and_add(val, 0);
}
/**
* Compare and swap: atomically compare *val with expected, and if equal,
* set *val to desired. Returns true if the swap occurred.
*
* Uses sequential consistency (ATOMIC_SEQ_CST / InterlockedCompareExchange)
* to ensure all memory operations are visible across threads. This is
* appropriate for thread synchronization and state machine transitions.
*
* Note: The ABA problem (where a value changes A->B->A between reads) is not
* a concern for simple integer-based state machines with monotonic transitions.
*/
static inline bool
sentry__atomic_compare_swap(volatile long *val, long expected, long desired)
{
#ifdef SENTRY_PLATFORM_WINDOWS
# if SIZEOF_LONG == 8
return InterlockedCompareExchange64((LONG64 *)val, desired, expected)
== expected;
# else
return InterlockedCompareExchange((LONG *)val, desired, expected)
== expected;
# endif
#else
return __atomic_compare_exchange_n(
val, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#endif
}
struct sentry_bgworker_s;
typedef struct sentry_bgworker_s sentry_bgworker_t;
typedef void (*sentry_task_exec_func_t)(void *task_data, void *state);
/**
* Creates a new background worker thread.
*
* This moves ownership of `state` into the background worker, which uses the
* given `free_state` function to free that state.
*/
sentry_bgworker_t *sentry__bgworker_new(
void *state, void (*free_state)(void *state));
/**
* Returns a reference to the state of the worker.
*/
void *sentry__bgworker_get_state(sentry_bgworker_t *bgw);
/**
* Drops the reference to the background worker.
*/
void sentry__bgworker_decref(sentry_bgworker_t *bgw);
/**
* Start a new background worker thread associated with `bgw`.
* Returns 0 on success.
*/
int sentry__bgworker_start(sentry_bgworker_t *bgw);
/**
* This will try to flush the background worker thread queue, with a `timeout`.
* Returns 0 on success.
*/
int sentry__bgworker_flush(sentry_bgworker_t *bgw, uint64_t timeout);
/**
* This will try to shut down the background worker thread, with a `timeout`.
* Returns 0 on success.
*/
int sentry__bgworker_shutdown(sentry_bgworker_t *bgw, uint64_t timeout);
/**
* This will set a preferable thread name for background worker.
* Should be executed before worker start
*/
void sentry__bgworker_setname(sentry_bgworker_t *bgw, const char *thread_name);
#ifdef SENTRY_UNITTEST
/**
* Test helper function to get the thread name from a bgworker.
* Only available in unit tests.
*/
const char *sentry__bgworker_get_thread_name(sentry_bgworker_t *bgw);
#endif
/**
* This will submit a new task to the background thread.
*
* The `_delayed` variant delays execution by the specified delay in
* milliseconds, and the `_at` variant executes after the specified monotonic
* timestamp. The latter is mostly useful for testing to ensure deterministic
* ordering of tasks regardless of OS preemption between submissions.
*
* Takes ownership of `data`, freeing it using the provided `cleanup_func`.
* Returns 0 on success.
*/
int sentry__bgworker_submit(sentry_bgworker_t *bgw,
sentry_task_exec_func_t exec_func, void (*cleanup_func)(void *task_data),
void *task_data);
int sentry__bgworker_submit_delayed(sentry_bgworker_t *bgw,
sentry_task_exec_func_t exec_func, void (*cleanup_func)(void *task_data),
void *task_data, uint64_t delay_ms);
int sentry__bgworker_submit_at(sentry_bgworker_t *bgw,
sentry_task_exec_func_t exec_func, void (*cleanup_func)(void *task_data),
void *task_data, uint64_t execute_after);
/**
* This function will iterate through all the current tasks of the worker
* thread, and will call the `callback` function for each task with a matching
* `exec_func`. The callback can return `true` to indicate if the current task
* should be dropped from the queue.
* The function will return the number of dropped tasks.
*/
size_t sentry__bgworker_foreach_matching(sentry_bgworker_t *bgw,
sentry_task_exec_func_t exec_func,
bool (*callback)(void *task_data, void *data), void *data);
/**
* A level-triggered waitable atomic flag.
*
* The flag has two states: 0 (unset) and 1 (set).
* - `sentry__waitable_flag_set`: atomically sets the flag to 1 and wakes
* a waiting thread.
* - `sentry__waitable_flag_wait`: waits until the flag is set or the timeout
* expires, then atomically clears the flag. Returns true if the flag was
* set, false on timeout.
* - On Windows 8+/Xbox uses WaitOnAddress, on Linux and Android a futex to keep
* trigger latency low and pass on wait to the OS. Other POSIX, older Windows
* fall back on a sleepy atomic poll loop.
*/
#if defined(SENTRY_PLATFORM_WINDOWS) && _WIN32_WINNT >= 0x0602
typedef struct {
volatile LONG value;
} sentry_waitable_flag_t;
static inline void
sentry__waitable_flag_init(sentry_waitable_flag_t *flag)
{
flag->value = 0;
}
static inline void
sentry__waitable_flag_set(sentry_waitable_flag_t *flag)
{
InterlockedExchange(&flag->value, 1);
WakeByAddressSingle((PVOID)&flag->value);
}
static inline bool
sentry__waitable_flag_wait(sentry_waitable_flag_t *flag, uint64_t timeout_ms)
{
LONG undesired = 0;
// Fast path: flag already set
if (InterlockedCompareExchange(&flag->value, 0, 1) == 1) {
return true;
}
// WaitOnAddress blocks while *address == undesired
WaitOnAddress((volatile VOID *)&flag->value, &undesired, sizeof(LONG),
(DWORD)timeout_ms);
// Try to consume the flag regardless of wait result
return InterlockedCompareExchange(&flag->value, 0, 1) == 1;
}
#elif defined(SENTRY_PLATFORM_LINUX)
# include <linux/futex.h>
# include <sys/syscall.h>
# include <unistd.h>
typedef struct {
volatile int value;
} sentry_waitable_flag_t;
static inline void
sentry__waitable_flag_init(sentry_waitable_flag_t *flag)
{
flag->value = 0;
}
static inline void
sentry__waitable_flag_set(sentry_waitable_flag_t *flag)
{
__atomic_store_n(&flag->value, 1, __ATOMIC_SEQ_CST);
syscall(SYS_futex, &flag->value, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
}
static inline bool
sentry__waitable_flag_wait(sentry_waitable_flag_t *flag, uint64_t timeout_ms)
{
// Fast path: flag already set, atomically consume it
int expected = 1;
if (__atomic_compare_exchange_n(&flag->value, &expected, 0, false,
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
return true;
}
struct timespec ts;
ts.tv_sec = (time_t)(timeout_ms / 1000);
ts.tv_nsec = (long)((timeout_ms % 1000) * 1000000);
// Block while value == 0
syscall(SYS_futex, &flag->value, FUTEX_WAIT_PRIVATE, 0, &ts, NULL, 0);
// Try to consume the flag
expected = 1;
return __atomic_compare_exchange_n(
&flag->value, &expected, 0, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
#elif defined(SENTRY_PLATFORM_WINDOWS)
/* Fallback for older Windows: sleep-poll with Sleep(). */
typedef struct {
volatile LONG value;
} sentry_waitable_flag_t;
static inline void
sentry__waitable_flag_init(sentry_waitable_flag_t *flag)
{
InterlockedExchange(&flag->value, 0);
}
static inline void
sentry__waitable_flag_set(sentry_waitable_flag_t *flag)
{
InterlockedExchange(&flag->value, 1);
}
static inline bool
sentry__waitable_flag_wait(sentry_waitable_flag_t *flag, uint64_t timeout_ms)
{
ULONGLONG deadline = GetTickCount64() + (ULONGLONG)timeout_ms;
while (!InterlockedCompareExchange(&flag->value, 0, 1)) {
if (GetTickCount64() >= deadline) {
return false;
}
Sleep(10); // 10ms
}
return true;
}
#else
/* Fallback for other POSIX platforms (Darwin, FreeBSD, AIX, NX, etc.).
* Uses a simple sleep-poll loop over an atomic flag. Trades up to 10ms of
* wake latency for zero platform-specific dependencies. */
# include <unistd.h>
typedef struct {
volatile int value;
} sentry_waitable_flag_t;
static inline void
sentry__waitable_flag_init(sentry_waitable_flag_t *flag)
{
__atomic_store_n(&flag->value, 0, __ATOMIC_SEQ_CST);
}
static inline void
sentry__waitable_flag_set(sentry_waitable_flag_t *flag)
{
__atomic_store_n(&flag->value, 1, __ATOMIC_RELEASE);
}
static inline bool
sentry__waitable_flag_wait(sentry_waitable_flag_t *flag, uint64_t timeout_ms)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t deadline_ms = (uint64_t)ts.tv_sec * 1000
+ (uint64_t)ts.tv_nsec / 1000000 + timeout_ms;
int expected = 1;
while (!__atomic_compare_exchange_n(&flag->value, &expected, 0, false,
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
expected = 1;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now_ms
= (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
if (now_ms >= deadline_ms) {
return false;
}
usleep(10000); // 10ms
}
return true;
}
#endif
#endif