Skip to content

Commit 572aeb5

Browse files
authored
Merge pull request #12882 from gadmm/systhread_yield
Execute preemptive systhread switching as a delayed pending action
2 parents 1628c38 + f22731b commit 572aeb5

6 files changed

Lines changed: 65 additions & 32 deletions

File tree

Changes

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ OCaml 5.2.0
221221
(Guillaume Munch-Maccagnoni, review by Anil Madhavapeddy and KC
222222
Sivaramakrishnan)
223223

224+
- #12875, #12879, #12882: Execute preemptive systhread switching as a
225+
delayed pending action. This ensures that one can reason within the
226+
FFI that no mutation happens on the same domain when allocating on
227+
the OCaml heap from C, consistently with OCaml 4. This also fixes
228+
further bugs with the multicore systhreads implementation.
229+
(Guillaume Munch-Maccagnoni, bug reports and suggestion by Mark
230+
Shinwell, review by Nick Barnes and Stephen Dolan)
231+
224232
- #12408: `Domain.spawn` no longer leaks its functional argument for
225233
the whole duration of the children domain lifetime.
226234
(Guillaume Munch-Maccagnoni, review by Gabriel Scherer)
@@ -247,7 +255,7 @@ OCaml 5.2.0
247255

248256
- #11307: Finish adapting the implementation of asynchronous actions for
249257
multicore: soundness, liveness, and performance issues.
250-
Do not crash if a signal handler is called from an unregistered C
258+
Do not crash if a signal handler is called from an unregistered C
251259
thread, and other possible soundness issues. Prevent issues where join
252260
on other domains could make the toplevel unresponsible to Ctrl-C. Avoid
253261
needless repeated polling in C code when callbacks cannot run

manual/src/cmds/intf-c.etex

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,16 +1268,18 @@ has taken place since "r" was allocated.
12681268

12691269
\subsection{ss:c-process-pending-actions}{Pending actions and asynchronous exceptions}
12701270

1271-
Since 4.10, allocation functions are guaranteed not to call any OCaml
1272-
callbacks from C, including finalisers and signal handlers, and delay
1273-
their execution instead.
1271+
Since 4.10, allocation functions are guaranteed not to run any OCaml
1272+
code, including finalisers, signal handlers and other threads running
1273+
on the same domain. Instead, their execution is delayed to a later
1274+
safe point.
12741275

12751276
The function \verb"caml_process_pending_actions" from
12761277
"<caml/signals.h>" executes any pending signal handlers and
1277-
finalisers, Memprof callbacks, and requested minor and major garbage
1278-
collections. In particular, it can raise asynchronous exceptions. It
1279-
is recommended to call it regularly at safe points inside long-running
1280-
non-blocking C code.
1278+
finalisers, Memprof callbacks, preemptive systhread switching, and
1279+
requested minor and major garbage collections. In particular, it can
1280+
raise asynchronous exceptions and cause mutations on the OCaml heap
1281+
from the same domain. It is recommended to call it regularly at safe
1282+
points inside long-running non-blocking C code.
12811283

12821284
The variant \verb"caml_process_pending_actions_exn" is provided, that
12831285
returns the exception instead of raising it directly into OCaml code.

otherlibs/systhreads/st_stubs.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ static void caml_thread_domain_initialize_hook(void)
509509
caml_memprof_enter_thread(new_thread->memprof);
510510
}
511511

512-
CAMLprim value caml_thread_yield(value unit);
512+
static void thread_yield(void);
513513

514514
void caml_thread_interrupt_hook(void)
515515
{
@@ -521,7 +521,7 @@ void caml_thread_interrupt_hook(void)
521521
&Caml_state->requested_external_interrupt;
522522

523523
if (atomic_compare_exchange_strong(req_external_interrupt, &is_on, 0)) {
524-
caml_thread_yield(Val_unit);
524+
thread_yield();
525525
}
526526

527527
return;
@@ -780,26 +780,33 @@ CAMLprim value caml_thread_uncaught_exception(value exn)
780780

781781
/* Allow re-scheduling */
782782

783-
CAMLprim value caml_thread_yield(value unit)
783+
static void thread_yield(void)
784784
{
785785
st_masterlock *m = Thread_lock(Caml_state->id);
786786
if (st_masterlock_waiters(m) == 0)
787-
return Val_unit;
787+
return;
788788

789789
/* Do all the parts of a blocking section enter&leave except lock
790790
manipulation, which we will do more efficiently in
791-
st_thread_yield, and asynchronous actions (since
792-
[caml_thread_yield] must not raise). (Since our blocking section
793-
does not contain anything interesting, do not bother saving
794-
errno.)
791+
st_thread_yield, and executing signal handlers (which is already
792+
done at this point as part of the asynchronous actions mechanism
793+
when forcing a systhread yield). (Since our blocking section does
794+
not contain anything interesting, do not bother saving errno.)
795795
*/
796796

797797
save_runtime_state();
798798
st_thread_yield(m);
799799
restore_runtime_state(This_thread);
800+
801+
/* Switching threads might have unmasked some signal. */
800802
if (caml_check_pending_signals())
801803
caml_set_action_pending(Caml_state);
804+
}
802805

806+
CAMLprim value caml_thread_yield(value unit)
807+
{
808+
caml_process_pending_actions();
809+
thread_yield();
803810
return Val_unit;
804811
}
805812

runtime/caml/domain.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ int caml_incoming_interrupts_queued(void);
6262

6363
void caml_poll_gc_work(void);
6464
void caml_handle_gc_interrupt(void);
65+
void caml_process_external_interrupt(void);
6566
void caml_handle_incoming_interrupts(void);
6667

6768
CAMLextern void caml_interrupt_self(void);

runtime/domain.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,20 +1644,22 @@ void caml_reset_young_limit(caml_domain_state * dom_st)
16441644
achieves the proper synchronisation. */
16451645
atomic_exchange(&dom_st->young_limit, (uintnat)trigger);
16461646

1647+
/* For non-delayable asynchronous actions, we immediately interrupt
1648+
the domain again. */
16471649
dom_internal * d = &all_domains[dom_st->id];
16481650
if (atomic_load_relaxed(&d->interruptor.interrupt_pending)
16491651
|| dom_st->requested_minor_gc
16501652
|| dom_st->requested_major_slice
1651-
|| dom_st->major_slice_epoch < atomic_load (&caml_major_slice_epoch)
1652-
|| atomic_load_relaxed(&dom_st->requested_external_interrupt)) {
1653+
|| dom_st->major_slice_epoch < atomic_load (&caml_major_slice_epoch)) {
16531654
interrupt_domain_local(dom_st);
16541655
}
1655-
/* We might be here due to a recently-recorded signal, so we
1656-
need to remember that we must run signal handlers. In
1657-
addition, in the case of long-running C code (that may
1658-
regularly poll with caml_process_pending_actions), we want to
1659-
force a query of all callbacks at every minor collection or
1660-
major slice (similarly to the OCaml behaviour). */
1656+
/* We might be here due to a recently-recorded signal or forced
1657+
systhread switching, so we need to remember that we must run
1658+
signal handlers or systhread's yield. In addition, in the case of
1659+
long-running C code (that may regularly poll with
1660+
caml_process_pending_actions), we want to force a query of all
1661+
callbacks at every minor collection or major slice (similarly to
1662+
the OCaml behaviour). */
16611663
caml_set_action_pending(dom_st);
16621664
}
16631665

@@ -1749,9 +1751,6 @@ void caml_poll_gc_work(void)
17491751
caml_poll_gc_work is called. */
17501752
}
17511753

1752-
if (atomic_load_acquire(&d->requested_external_interrupt)) {
1753-
caml_domain_external_interrupt_hook();
1754-
}
17551754
caml_reset_young_limit(d);
17561755
}
17571756

@@ -1769,6 +1768,14 @@ void caml_handle_gc_interrupt(void)
17691768
caml_poll_gc_work();
17701769
}
17711770

1771+
/* Preemptive systhread switching */
1772+
void caml_process_external_interrupt(void)
1773+
{
1774+
if (atomic_load_acquire(&Caml_state->requested_external_interrupt)) {
1775+
caml_domain_external_interrupt_hook();
1776+
}
1777+
}
1778+
17721779
CAMLexport int caml_bt_is_in_blocking_section(void)
17731780
{
17741781
uintnat status = atomic_load_acquire(&domain_self->backup_thread_msg);

runtime/signals.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,13 @@ void caml_request_minor_gc (void)
277277
There are two kinds of asynchronous actions:
278278
279279
- Those that we execute immediately in all circumstances (STW
280-
interrupts, requested minor or major GC, forced systhread yield);
281-
they must never call OCaml code.
280+
interrupts, requested minor or major GC); they must never call
281+
OCaml code.
282282
283283
- Those that run OCaml code and may raise OCaml exceptions
284-
(asynchronous callbacks, finalisers, memprof callbacks); those
285-
can be delayed, and do not run during allocations from C.
284+
(asynchronous callbacks, finalisers, memprof callbacks, forced
285+
systhread yield); those can be delayed, and do not run during
286+
allocations from C.
286287
287288
Queued asynchronous actions are notified to the domain by setting
288289
[young_limit] to a high value, thereby making the next allocation
@@ -341,7 +342,8 @@ value caml_do_pending_actions_exn(void)
341342
caml_handle_gc_interrupt();
342343
/* [young_limit] has now been reset. */
343344

344-
/* 2. Delayable actions that may raise OCaml exceptions.
345+
/* 2. Delayable actions that may run OCaml code and raise OCaml
346+
exceptions.
345347
346348
We can now clear the action_pending flag since we are going to
347349
execute all actions. */
@@ -361,6 +363,12 @@ value caml_do_pending_actions_exn(void)
361363
exn = caml_final_do_calls_exn();
362364
if (Is_exception_result(exn)) goto exception;
363365

366+
/* Process external interrupts (e.g. preemptive systhread switching).
367+
By doing this last, we do not need to set the action pending flag
368+
in case a context switch happens: all actions have been processed
369+
at this point. */
370+
caml_process_external_interrupt();
371+
364372
return Val_unit;
365373

366374
exception:

0 commit comments

Comments
 (0)