Skip to content

Commit 3519f19

Browse files
skarupkeMalte Skarupke
andauthored
Use a custom condition variable implementation (#787)
Co-authored-by: Malte Skarupke <mskarupke@janestreet.com>
1 parent 94567d1 commit 3519f19

File tree

1 file changed

+94
-18
lines changed

1 file changed

+94
-18
lines changed

ocaml/otherlibs/systhreads/st_posix.h

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
#include <time.h>
2626
#include <sys/time.h>
2727
#ifdef __linux__
28+
#include <features.h>
2829
#include <unistd.h>
30+
#include <sys/syscall.h>
31+
#include <linux/futex.h>
32+
#include <limits.h>
2933
#endif
3034

3135
typedef int st_retcode;
@@ -109,6 +113,78 @@ Caml_inline void st_thread_set_id(intnat id)
109113
return;
110114
}
111115

116+
/* If we're using glibc, use a custom condition variable implementation to
117+
avoid this bug: https://sourceware.org/bugzilla/show_bug.cgi?id=25847
118+
119+
For now we only have this on linux because it directly uses the linux futex
120+
syscalls. */
121+
#if defined(__linux__) && defined(__GNU_LIBRARY__) && defined(__GLIBC__) && defined(__GLIBC_MINOR__)
122+
typedef struct {
123+
volatile unsigned counter;
124+
} custom_condvar;
125+
126+
static int custom_condvar_init(custom_condvar * cv)
127+
{
128+
cv->counter = 0;
129+
return 0;
130+
}
131+
132+
static int custom_condvar_destroy(custom_condvar * cv)
133+
{
134+
return 0;
135+
}
136+
137+
static int custom_condvar_wait(custom_condvar * cv, pthread_mutex_t * mutex)
138+
{
139+
unsigned old_count = cv->counter;
140+
pthread_mutex_unlock(mutex);
141+
syscall(SYS_futex, &cv->counter, FUTEX_WAIT_PRIVATE, old_count, NULL, NULL, 0);
142+
pthread_mutex_lock(mutex);
143+
return 0;
144+
}
145+
146+
static int custom_condvar_signal(custom_condvar * cv)
147+
{
148+
__sync_add_and_fetch(&cv->counter, 1);
149+
syscall(SYS_futex, &cv->counter, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
150+
return 0;
151+
}
152+
153+
static int custom_condvar_broadcast(custom_condvar * cv)
154+
{
155+
__sync_add_and_fetch(&cv->counter, 1);
156+
syscall(SYS_futex, &cv->counter, FUTEX_WAKE_PRIVATE, INT_MAX, NULL, NULL, 0);
157+
return 0;
158+
}
159+
#else
160+
typedef pthread_cond_t custom_condvar;
161+
162+
static int custom_condvar_init(custom_condvar * cv)
163+
{
164+
return pthread_cond_init(cv, NULL);
165+
}
166+
167+
static int custom_condvar_destroy(custom_condvar * cv)
168+
{
169+
return pthread_cond_destroy(cv);
170+
}
171+
172+
static int custom_condvar_wait(custom_condvar * cv, pthread_mutex_t * mutex)
173+
{
174+
return pthread_cond_wait(cv, mutex);
175+
}
176+
177+
static int custom_condvar_signal(custom_condvar * cv)
178+
{
179+
return pthread_cond_signal(cv);
180+
}
181+
182+
static int custom_condvar_broadcast(custom_condvar * cv)
183+
{
184+
return pthread_cond_broadcast(cv);
185+
}
186+
#endif
187+
112188
/* The master lock. This is a mutex that is held most of the time,
113189
so we implement it in a slightly convoluted way to avoid
114190
all risks of busy-waiting. Also, we count the number of waiting
@@ -118,13 +194,13 @@ typedef struct {
118194
pthread_mutex_t lock; /* to protect contents */
119195
int busy; /* 0 = free, 1 = taken */
120196
volatile int waiters; /* number of threads waiting on master lock */
121-
pthread_cond_t is_free; /* signaled when free */
197+
custom_condvar is_free; /* signaled when free */
122198
} st_masterlock;
123199

124200
static void st_masterlock_init(st_masterlock * m)
125201
{
126202
pthread_mutex_init(&m->lock, NULL);
127-
pthread_cond_init(&m->is_free, NULL);
203+
custom_condvar_init(&m->is_free);
128204
m->busy = 1;
129205
m->waiters = 0;
130206
}
@@ -134,7 +210,7 @@ static void st_masterlock_acquire(st_masterlock * m)
134210
pthread_mutex_lock(&m->lock);
135211
while (m->busy) {
136212
m->waiters ++;
137-
pthread_cond_wait(&m->is_free, &m->lock);
213+
custom_condvar_wait(&m->is_free, &m->lock);
138214
m->waiters --;
139215
}
140216
m->busy = 1;
@@ -146,7 +222,7 @@ static void st_masterlock_release(st_masterlock * m)
146222
pthread_mutex_lock(&m->lock);
147223
m->busy = 0;
148224
pthread_mutex_unlock(&m->lock);
149-
pthread_cond_signal(&m->is_free);
225+
custom_condvar_signal(&m->is_free);
150226
}
151227

152228
CAMLno_tsan /* This can be called for reading [waiters] without locking. */
@@ -179,14 +255,14 @@ Caml_inline void st_thread_yield(st_masterlock * m)
179255
}
180256

181257
m->busy = 0;
182-
pthread_cond_signal(&m->is_free);
258+
custom_condvar_signal(&m->is_free);
183259
m->waiters++;
184260
do {
185261
/* Note: the POSIX spec prevents the above signal from pairing with this
186262
wait, which is good: we'll reliably continue waiting until the next
187263
yield() or enter_blocking_section() call (or we see a spurious condvar
188264
wakeup, which are rare at best.) */
189-
pthread_cond_wait(&m->is_free, &m->lock);
265+
custom_condvar_wait(&m->is_free, &m->lock);
190266
} while (m->busy);
191267
m->busy = 1;
192268
m->waiters--;
@@ -254,14 +330,14 @@ Caml_inline int st_mutex_unlock(st_mutex m)
254330

255331
/* Condition variables */
256332

257-
typedef pthread_cond_t * st_condvar;
333+
typedef custom_condvar * st_condvar;
258334

259335
static int st_condvar_create(st_condvar * res)
260336
{
261337
int rc;
262-
st_condvar c = caml_stat_alloc_noexc(sizeof(pthread_cond_t));
338+
st_condvar c = caml_stat_alloc_noexc(sizeof(custom_condvar));
263339
if (c == NULL) return ENOMEM;
264-
rc = pthread_cond_init(c, NULL);
340+
rc = custom_condvar_init(c);
265341
if (rc != 0) { caml_stat_free(c); return rc; }
266342
*res = c;
267343
return 0;
@@ -270,32 +346,32 @@ static int st_condvar_create(st_condvar * res)
270346
static int st_condvar_destroy(st_condvar c)
271347
{
272348
int rc;
273-
rc = pthread_cond_destroy(c);
349+
rc = custom_condvar_destroy(c);
274350
caml_stat_free(c);
275351
return rc;
276352
}
277353

278354
Caml_inline int st_condvar_signal(st_condvar c)
279355
{
280-
return pthread_cond_signal(c);
356+
return custom_condvar_signal(c);
281357
}
282358

283359
Caml_inline int st_condvar_broadcast(st_condvar c)
284360
{
285-
return pthread_cond_broadcast(c);
361+
return custom_condvar_broadcast(c);
286362
}
287363

288364
Caml_inline int st_condvar_wait(st_condvar c, st_mutex m)
289365
{
290-
return pthread_cond_wait(c, m);
366+
return custom_condvar_wait(c, m);
291367
}
292368

293369
/* Triggered events */
294370

295371
typedef struct st_event_struct {
296372
pthread_mutex_t lock; /* to protect contents */
297373
int status; /* 0 = not triggered, 1 = triggered */
298-
pthread_cond_t triggered; /* signaled when triggered */
374+
custom_condvar triggered; /* signaled when triggered */
299375
} * st_event;
300376

301377
static int st_event_create(st_event * res)
@@ -305,7 +381,7 @@ static int st_event_create(st_event * res)
305381
if (e == NULL) return ENOMEM;
306382
rc = pthread_mutex_init(&e->lock, NULL);
307383
if (rc != 0) { caml_stat_free(e); return rc; }
308-
rc = pthread_cond_init(&e->triggered, NULL);
384+
rc = custom_condvar_init(&e->triggered);
309385
if (rc != 0)
310386
{ pthread_mutex_destroy(&e->lock); caml_stat_free(e); return rc; }
311387
e->status = 0;
@@ -317,7 +393,7 @@ static int st_event_destroy(st_event e)
317393
{
318394
int rc1, rc2;
319395
rc1 = pthread_mutex_destroy(&e->lock);
320-
rc2 = pthread_cond_destroy(&e->triggered);
396+
rc2 = custom_condvar_destroy(&e->triggered);
321397
caml_stat_free(e);
322398
return rc1 != 0 ? rc1 : rc2;
323399
}
@@ -330,7 +406,7 @@ static int st_event_trigger(st_event e)
330406
e->status = 1;
331407
rc = pthread_mutex_unlock(&e->lock);
332408
if (rc != 0) return rc;
333-
rc = pthread_cond_broadcast(&e->triggered);
409+
rc = custom_condvar_broadcast(&e->triggered);
334410
return rc;
335411
}
336412

@@ -340,7 +416,7 @@ static int st_event_wait(st_event e)
340416
rc = pthread_mutex_lock(&e->lock);
341417
if (rc != 0) return rc;
342418
while(e->status == 0) {
343-
rc = pthread_cond_wait(&e->triggered, &e->lock);
419+
rc = custom_condvar_wait(&e->triggered, &e->lock);
344420
if (rc != 0) return rc;
345421
}
346422
rc = pthread_mutex_unlock(&e->lock);

0 commit comments

Comments
 (0)