M:N thread scheduler for Ractors

This patch introduce M:N thread scheduler for Ractor system.

In general, M:N thread scheduler employs N native threads (OS threads)
to manage M user-level threads (Ruby threads in this case).
On the Ruby interpreter, 1 native thread is provided for 1 Ractor
and all Ruby threads are managed by the native thread.

From Ruby 1.9, the interpreter uses 1:1 thread scheduler which means
1 Ruby thread has 1 native thread. M:N scheduler change this strategy.

Because of compatibility issue (and stableness issue of the implementation)
main Ractor doesn't use M:N scheduler on default. On the other words,
threads on the main Ractor will be managed with 1:1 thread scheduler.

There are additional settings by environment variables:

`RUBY_MN_THREADS=1` enables M:N thread scheduler on the main ractor.
Note that non-main ractors use the M:N scheduler without this
configuration. With this configuration, single ractor applications
run threads on M:1 thread scheduler (green threads, user-level threads).

`RUBY_MAX_CPU=n` specifies maximum number of native threads for
M:N scheduler (default: 8).

This patch will be reverted soon if non-easy issues are found.

[Bug #19842]
This commit is contained in:
Koichi Sasada 2023-04-10 10:53:13 +09:00
parent 096ee0648e
commit be1bbd5b7d
27 changed files with 3580 additions and 1524 deletions

View file

@ -685,10 +685,16 @@ rb_last_status_set(int status, rb_pid_t pid)
GET_THREAD()->last_status = rb_process_status_new(pid, status, 0);
}
static void
last_status_clear(rb_thread_t *th)
{
th->last_status = Qnil;
}
void
rb_last_status_clear(void)
{
GET_THREAD()->last_status = Qnil;
last_status_clear(GET_THREAD());
}
static rb_pid_t
@ -1654,24 +1660,11 @@ before_exec(void)
before_exec_async_signal_safe();
}
/* This function should be async-signal-safe. Actually it is. */
static void
after_exec_async_signal_safe(void)
{
}
static void
after_exec_non_async_signal_safe(void)
{
rb_thread_reset_timer_thread();
rb_thread_start_timer_thread();
}
static void
after_exec(void)
{
after_exec_async_signal_safe();
after_exec_non_async_signal_safe();
rb_thread_reset_timer_thread();
rb_thread_start_timer_thread();
}
#if defined HAVE_WORKING_FORK || defined HAVE_DAEMON
@ -1686,10 +1679,14 @@ after_fork_ruby(rb_pid_t pid)
{
rb_threadptr_pending_interrupt_clear(GET_THREAD());
if (pid == 0) {
// child
clear_pid_cache();
rb_thread_atfork();
}
after_exec();
else {
// parent
after_exec();
}
}
#endif
@ -4210,16 +4207,19 @@ rb_fork_ruby2(struct rb_process_status *status)
while (1) {
prefork();
disable_child_handler_before_fork(&old);
before_fork_ruby();
pid = rb_fork();
err = errno;
if (status) {
status->pid = pid;
status->error = err;
disable_child_handler_before_fork(&old);
{
pid = rb_fork();
err = errno;
if (status) {
status->pid = pid;
status->error = err;
}
}
after_fork_ruby(pid);
disable_child_handler_fork_parent(&old); /* yes, bad name */
after_fork_ruby(pid);
if (pid >= 0) { /* fork succeed */
return pid;
@ -4663,11 +4663,16 @@ static VALUE
do_spawn_process(VALUE arg)
{
struct spawn_args *argp = (struct spawn_args *)arg;
rb_execarg_parent_start1(argp->execarg);
return (VALUE)rb_spawn_process(DATA_PTR(argp->execarg),
argp->errmsg.ptr, argp->errmsg.buflen);
}
NOINLINE(static rb_pid_t
rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen));
static rb_pid_t
rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen)
{
@ -4676,8 +4681,10 @@ rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen)
args.execarg = execarg_obj;
args.errmsg.ptr = errmsg;
args.errmsg.buflen = errmsg_buflen;
return (rb_pid_t)rb_ensure(do_spawn_process, (VALUE)&args,
execarg_parent_end, execarg_obj);
rb_pid_t r = (rb_pid_t)rb_ensure(do_spawn_process, (VALUE)&args,
execarg_parent_end, execarg_obj);
return r;
}
static rb_pid_t
@ -4820,13 +4827,14 @@ rb_spawn(int argc, const VALUE *argv)
static VALUE
rb_f_system(int argc, VALUE *argv, VALUE _)
{
rb_thread_t *th = GET_THREAD();
VALUE execarg_obj = rb_execarg_new(argc, argv, TRUE, TRUE);
struct rb_execarg *eargp = rb_execarg_get(execarg_obj);
struct rb_process_status status = {0};
eargp->status = &status;
rb_last_status_clear();
last_status_clear(th);
// This function can set the thread's last status.
// May be different from waitpid_state.pid on exec failure.
@ -4834,12 +4842,10 @@ rb_f_system(int argc, VALUE *argv, VALUE _)
if (pid > 0) {
VALUE status = rb_process_status_wait(pid, 0);
struct rb_process_status *data = rb_check_typeddata(status, &rb_process_status_type);
// Set the last status:
rb_obj_freeze(status);
GET_THREAD()->last_status = status;
th->last_status = status;
if (data->status == EXIT_SUCCESS) {
return Qtrue;