Skip to content

Commit 11a5de7

Browse files
committed
Introduce an API to swap the runtime lock for a different lock.
See caml_switch_runtime_locking_scheme in threads.h
1 parent bc7f2d7 commit 11a5de7

File tree

7 files changed

+297
-36
lines changed

7 files changed

+297
-36
lines changed

ocaml/otherlibs/systhreads/st_posix.h

+14
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,20 @@ Caml_inline void st_thread_yield(st_masterlock * m)
269269
pthread_mutex_unlock(&m->lock);
270270
}
271271

272+
Caml_inline void st_fallback_yield()
273+
{
274+
#ifdef __linux__
275+
/* sched_yield() doesn't do what we want in Linux 2.6 and up (PR#2663) */
276+
/* but not doing anything here would actually disable preemption (PR#7669) */
277+
struct timespec t;
278+
t.tv_sec = 0;
279+
t.tv_nsec = 1;
280+
nanosleep(&t, NULL);
281+
#else
282+
sched_yield();
283+
#endif
284+
}
285+
272286
/* Mutexes */
273287

274288
typedef pthread_mutex_t * st_mutex;

ocaml/otherlibs/systhreads/st_stubs.c

+109-33
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
#include "caml/sys.h"
3838
#include "caml/memprof.h"
3939

40-
/* threads.h is *not* included since it contains the _external_ declarations for
41-
the caml_c_thread_register and caml_c_thread_unregister functions. */
40+
#define CAMLextern_libthreads
41+
#include "threads.h"
4242

4343
#ifndef NATIVE_CODE
4444
/* Initial size of bytecode stack when a thread is created (4 Ko) */
@@ -55,6 +55,23 @@
5555
#include "st_posix.h"
5656
#endif
5757

58+
/* Atomics */
59+
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8
60+
/* GCC 4.8 shipped with a working implementation of atomics, but no
61+
stdatomic.h header, so we need to use GCC-specific intrinsics. */
62+
63+
#define _Atomic /* GCC intrinsics work on normal variables */
64+
#define atomic_store(v, x) \
65+
__atomic_store_n((v), (x), __ATOMIC_SEQ_CST)
66+
#define atomic_load(v) \
67+
__atomic_load_n((v), __ATOMIC_SEQ_CST)
68+
#define atomic_exchange(v, x) \
69+
__atomic_exchange_n((v), (x), __ATOMIC_SEQ_CST)
70+
#else
71+
#include <stdatomic.h>
72+
#endif
73+
74+
5875
/* The ML value describing a thread (heap-allocated) */
5976

6077
struct caml_thread_descr {
@@ -111,7 +128,7 @@ static caml_thread_t all_threads = NULL;
111128
static caml_thread_t curr_thread = NULL;
112129

113130
/* The master lock protecting the OCaml runtime system */
114-
static st_masterlock caml_master_lock;
131+
static _Atomic struct caml_locking_scheme* caml_locking_scheme;
115132

116133
/* Whether the "tick" thread is already running */
117134
static int caml_tick_thread_running = 0;
@@ -143,6 +160,36 @@ extern struct longjmp_buffer caml_termination_jmpbuf;
143160
extern void (*caml_termination_hook)(void);
144161
#endif
145162

163+
/* The default locking scheme */
164+
static st_masterlock default_master_lock;
165+
struct caml_locking_scheme default_locking_scheme =
166+
{ &default_master_lock,
167+
(void (*)(void*))&st_masterlock_acquire,
168+
(void (*)(void*))&st_masterlock_release,
169+
(void (*)(void*))&st_masterlock_init,
170+
(int (*)(void*))&st_masterlock_waiters,
171+
(void (*)(void*))&st_thread_yield };
172+
173+
static void acquire_runtime_lock()
174+
{
175+
struct caml_locking_scheme* s;
176+
177+
retry:
178+
s = atomic_load(&caml_locking_scheme);
179+
s->lock(s->context);
180+
if (atomic_load(&caml_locking_scheme) != s) {
181+
s->unlock(s->context);
182+
goto retry;
183+
}
184+
}
185+
186+
static void release_runtime_lock()
187+
{
188+
struct caml_locking_scheme* s;
189+
s = atomic_load(&caml_locking_scheme);
190+
s->unlock(s->context);
191+
}
192+
146193
/* Hook for scanning the stacks of the other threads */
147194

148195
static void (*prev_scan_roots_hook) (scanning_action);
@@ -182,7 +229,7 @@ static void memprof_ctx_iter(th_ctx_action f, void* data)
182229

183230
/* Saving and restoring runtime state in curr_thread */
184231

185-
Caml_inline void caml_thread_save_runtime_state(void)
232+
CAMLexport void caml_thread_save_runtime_state(void)
186233
{
187234
#ifdef NATIVE_CODE
188235
curr_thread->top_of_stack = Caml_state->top_of_stack;
@@ -208,8 +255,12 @@ Caml_inline void caml_thread_save_runtime_state(void)
208255
caml_memprof_leave_thread();
209256
}
210257

211-
Caml_inline void caml_thread_restore_runtime_state(void)
258+
CAMLexport void caml_thread_restore_runtime_state(void)
212259
{
260+
/* Update curr_thread to point to the thread descriptor corresponding
261+
to the thread currently executing */
262+
curr_thread = st_tls_get(thread_descriptor_key);
263+
213264
#ifdef NATIVE_CODE
214265
Caml_state->top_of_stack = curr_thread->top_of_stack;
215266
Caml_state->bottom_of_stack= curr_thread->bottom_of_stack;
@@ -234,6 +285,19 @@ Caml_inline void caml_thread_restore_runtime_state(void)
234285
caml_memprof_enter_thread(curr_thread->memprof_ctx);
235286
}
236287

288+
CAMLexport void caml_switch_runtime_locking_scheme(struct caml_locking_scheme* new)
289+
{
290+
struct caml_locking_scheme* old;
291+
292+
caml_thread_save_runtime_state();
293+
old = atomic_exchange(&caml_locking_scheme, new);
294+
/* We hold 'old', but it is no longer the runtime lock */
295+
old->unlock(old->context);
296+
acquire_runtime_lock();
297+
caml_thread_restore_runtime_state();
298+
}
299+
300+
237301
/* Hooks for caml_enter_blocking_section and caml_leave_blocking_section */
238302

239303

@@ -243,7 +307,7 @@ static void caml_thread_enter_blocking_section(void)
243307
of the current thread */
244308
caml_thread_save_runtime_state();
245309
/* Tell other threads that the runtime is free */
246-
st_masterlock_release(&caml_master_lock);
310+
release_runtime_lock();
247311
}
248312

249313
static void caml_thread_leave_blocking_section(void)
@@ -255,11 +319,7 @@ static void caml_thread_leave_blocking_section(void)
255319
DWORD error = GetLastError();
256320
#endif
257321
/* Wait until the runtime is free */
258-
st_masterlock_acquire(&caml_master_lock);
259-
/* Update curr_thread to point to the thread descriptor corresponding
260-
to the thread currently executing */
261-
curr_thread = st_tls_get(thread_descriptor_key);
262-
/* Restore the runtime state from the curr_thread descriptor */
322+
acquire_runtime_lock();
263323
caml_thread_restore_runtime_state();
264324
#ifdef _WIN32
265325
SetLastError(error);
@@ -419,6 +479,7 @@ static void caml_thread_remove_info(caml_thread_t th)
419479
static void caml_thread_reinitialize(void)
420480
{
421481
struct channel * chan;
482+
struct caml_locking_scheme* s;
422483

423484
/* Remove all other threads (now nonexistent)
424485
from the doubly-linked list of threads */
@@ -430,7 +491,8 @@ static void caml_thread_reinitialize(void)
430491
/* Reinitialize the master lock machinery,
431492
just in case the fork happened while other threads were doing
432493
caml_leave_blocking_section */
433-
st_masterlock_init(&caml_master_lock);
494+
s = atomic_load(&caml_locking_scheme);
495+
s->reinitialize_after_fork(s->context);
434496
/* Tick thread is not currently running in child process, will be
435497
re-created at next Thread.create */
436498
caml_tick_thread_running = 0;
@@ -454,7 +516,8 @@ CAMLprim value caml_thread_initialize(value unit) /* ML */
454516
/* OS-specific initialization */
455517
st_initialize();
456518
/* Initialize and acquire the master lock */
457-
st_masterlock_init(&caml_master_lock);
519+
st_masterlock_init(&default_master_lock);
520+
caml_locking_scheme = &default_locking_scheme;
458521
/* Initialize the keys */
459522
st_tls_newkey(&thread_descriptor_key);
460523
st_tls_newkey(&last_channel_locked_key);
@@ -562,7 +625,7 @@ static void caml_thread_stop(void)
562625
/* OS-specific cleanups */
563626
st_thread_cleanup();
564627
/* Release the runtime system */
565-
st_masterlock_release(&caml_master_lock);
628+
release_runtime_lock();
566629
}
567630

568631
/* Create a thread */
@@ -658,7 +721,7 @@ CAMLexport int caml_c_thread_register(void)
658721
th->top_of_stack = (char *) &err;
659722
#endif
660723
/* Take master lock to protect access to the chaining of threads */
661-
st_masterlock_acquire(&caml_master_lock);
724+
acquire_runtime_lock();
662725
/* Add thread info block to the list of threads */
663726
if (all_threads == NULL) {
664727
th->next = th;
@@ -673,7 +736,7 @@ CAMLexport int caml_c_thread_register(void)
673736
/* Associate the thread descriptor with the thread */
674737
st_tls_set(thread_descriptor_key, (void *) th);
675738
/* Release the master lock */
676-
st_masterlock_release(&caml_master_lock);
739+
release_runtime_lock();
677740
/* Now we can re-enter the run-time system and heap-allocate the descriptor */
678741
caml_leave_blocking_section();
679742
th->descr = caml_thread_new_descriptor(Val_unit); /* no closure */
@@ -694,7 +757,7 @@ CAMLexport int caml_c_thread_unregister(void)
694757
/* Not registered? */
695758
if (th == NULL) return 0;
696759
/* Wait until the runtime is available */
697-
st_masterlock_acquire(&caml_master_lock);
760+
acquire_runtime_lock();
698761
/* Forget the thread descriptor */
699762
st_tls_set(thread_descriptor_key, NULL);
700763
/* Remove thread info block from list of threads, and free it */
@@ -703,7 +766,7 @@ CAMLexport int caml_c_thread_unregister(void)
703766
so that it does not prevent the whole process from exiting (#9971) */
704767
if (all_threads == NULL) caml_thread_cleanup(Val_unit);
705768
/* Release the runtime */
706-
st_masterlock_release(&caml_master_lock);
769+
release_runtime_lock();
707770
return 1;
708771
}
709772

@@ -771,21 +834,34 @@ CAMLprim value caml_thread_exit(value unit) /* ML */
771834

772835
CAMLprim value caml_thread_yield(value unit) /* ML */
773836
{
774-
if (st_masterlock_waiters(&caml_master_lock) == 0) return Val_unit;
775-
776-
/* Do all the parts of a blocking section enter/leave except lock
777-
manipulation, which we'll do more efficiently in st_thread_yield. (Since
778-
our blocking section doesn't contain anything interesting, don't bother
779-
with saving errno.)
780-
*/
781-
caml_raise_async_if_exception(caml_process_pending_signals_exn(),
782-
"signal handler");
783-
caml_thread_save_runtime_state();
784-
st_thread_yield(&caml_master_lock);
785-
curr_thread = st_tls_get(thread_descriptor_key);
786-
caml_thread_restore_runtime_state();
787-
caml_raise_async_if_exception(caml_process_pending_signals_exn(),
788-
"signal handler");
837+
struct caml_locking_scheme* s;
838+
839+
s = atomic_load(&caml_locking_scheme);
840+
if (s->count_waiters != NULL && s->yield != NULL) {
841+
if (s->count_waiters(s->context) == 0) return Val_unit;
842+
843+
/* Do all the parts of a blocking section enter/leave except lock
844+
manipulation, which we'll do more efficiently in st_thread_yield. (Since
845+
our blocking section doesn't contain anything interesting, don't bother
846+
with saving errno.)
847+
*/
848+
caml_raise_async_if_exception(caml_process_pending_signals_exn(),
849+
"signal handler");
850+
caml_thread_save_runtime_state();
851+
s->yield(s->context);
852+
if (atomic_load(&caml_locking_scheme) != s) {
853+
/* The lock we have is no longer the runtime lock */
854+
s->unlock(s->context);
855+
acquire_runtime_lock();
856+
}
857+
caml_thread_restore_runtime_state();
858+
caml_raise_async_if_exception(caml_process_pending_signals_exn(),
859+
"signal handler");
860+
} else {
861+
caml_enter_blocking_section();
862+
st_fallback_yield();
863+
caml_leave_blocking_section();
864+
}
789865

790866
return Val_unit;
791867
}

ocaml/otherlibs/systhreads/st_win32.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,18 @@ Caml_inline int st_masterlock_waiters(st_masterlock * m)
186186

187187
/* Scheduling hints */
188188

189-
Caml_inline void st_thread_yield(st_masterlock * m)
189+
Caml_inline void st_fallback_yield()
190190
{
191191
LeaveCriticalSection(m);
192192
Sleep(0);
193193
EnterCriticalSection(m);
194194
}
195195

196+
Caml_inline void st_thread_yield(st_masterlock * m)
197+
{
198+
st_fallback_yield();
199+
}
200+
196201
/* Mutexes */
197202

198203
struct st_mutex_ {

ocaml/otherlibs/systhreads/threads.h

+40-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ CAMLextern void caml_leave_blocking_section (void);
5050
use the runtime system (typically, a blocking I/O operation).
5151
*/
5252

53-
CAMLextern int caml_c_thread_register(void);
54-
CAMLextern int caml_c_thread_unregister(void);
53+
/* These functions are defined in the threads library, not the runtime */
54+
#ifndef CAMLextern_libthreads
55+
#define CAMLextern_libthreads CAMLextern
56+
#endif
57+
CAMLextern_libthreads int caml_c_thread_register(void);
58+
CAMLextern_libthreads int caml_c_thread_unregister(void);
5559

5660
/* If a thread is created by C code (instead of by OCaml itself),
5761
it must be registered with the OCaml runtime system before
@@ -61,6 +65,40 @@ CAMLextern int caml_c_thread_unregister(void);
6165
Both functions return 1 on success, 0 on error.
6266
*/
6367

68+
struct caml_locking_scheme {
69+
void* context;
70+
void (*lock)(void*);
71+
void (*unlock)(void*);
72+
73+
/* Called after fork().
74+
The lock should be held after this function returns. */
75+
void (*reinitialize_after_fork)(void*);
76+
77+
/* count_waiters and yield are used in a faster implementation of
78+
Thread.yield, but may be NULL (in which case a fallback is
79+
used) */
80+
int (*count_waiters)(void*);
81+
void (*yield)(void*);
82+
};
83+
84+
/* Switch to a new runtime locking scheme.
85+
86+
The old runtime lock must be held (i.e. not in a blocking section),
87+
and the new runtime lock must not be held. After this function
88+
returns, the old lock is released and the new one is held.
89+
90+
There is a period during this function when neither lock is held,
91+
so context-switches may occur. */
92+
CAMLextern_libthreads
93+
void caml_switch_runtime_locking_scheme(struct caml_locking_scheme*);
94+
95+
CAMLextern_libthreads
96+
void caml_thread_save_runtime_state(void);
97+
98+
CAMLextern_libthreads
99+
void caml_thread_restore_runtime_state(void);
100+
101+
64102
#ifdef __cplusplus
65103
}
66104
#endif

0 commit comments

Comments
 (0)