mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
Windows carried a second, much weaker scheduler: a single Win32 mutex as the GVL, vm->clock for preemption, its own VM barrier over vm->ractor.sync.barrier_*, its own Ractor wait/wakeup on a per-Ractor condvar, no ready queue and no time slice. Keeping two made the rest of the interpreter branch on which one was compiled in, and left Ractors on Windows without real scheduling. Move the scheduler out of thread_pthread.c into thread_sched.c and let both platforms use it. thread.c now includes thread_sched.c, which declares the scheduler <-> platform contract, includes THREAD_IMPL_SRC, and then builds the scheduler on the primitives that file provides. thread_pthread.c keeps only those primitives: synchronization, thread local storage, native thread creation and stacks, the communication pipe, the timer thread backend, the fork lock and the event hooks. The data structures the scheduler works on move to a new thread_sched.h, included from both platform headers, so struct rb_thread_sched, struct rb_native_thread and struct rb_thread_sched_item are now shared. thread_pthread_mn.c becomes thread_sched_mn.c: it is the M:N scheduler, not a pthread detail. Most of it -- the coroutine threads, the native thread stack pool, the timer wheel and the fd -> waiters map -- is platform independent; only the readiness backend is not, and that section is now marked as the seam where an epoll/kqueue split (or an IOCP backend, which is what Windows would need for M:N) would go. What Windows had to add is small: native_thread_interrupt(), native_thread_default_max_cpu(), timer_thread_polling(), timer_thread_wakeup_force(), RB_NATIVE_COND_MONOTONIC_P() and RB_NATIVE_MUTEX_TRYLOCK_DETECTS_SELF() -- the last because a CRITICAL_SECTION is recursive, so trylock cannot back a "somebody holds this" assertion. It sets USE_MN_THREADS to 0 and supplies the stubs thread_sched_mn.c provides elsewhere, so every thread there is dedicated, as it already was. With one scheduler the platform branches go away: all 16 uses of RUBY_THREAD_PTHREAD_H / RUBY_THREAD_WIN32_H are removed from vm_core.h, ractor.c, ractor_core.h, ractor_sync.c, vm_sync.c, vm.c and thread.c. ractor_check_blocking() went with them -- it only ever fed the win32 scheduler's vm->ractor.blocking_cnt -- and so did USE_VM_CLOCK, now that the common time slice drives preemption on Windows too. Verified on both platforms. Linux: 11 rounds of make test-all (36236 tests) and btest (2066 tests) with no failures, plus 474 runs of a scheduler and Ractor stress soak. Windows: no test fails that does not also fail on the same tree without this change, btest passes, and two 40-minute soaks of 438 and 460 runs are clean. Ractors now run in parallel there -- 3.6x on four Ractors, where before they could not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
379 lines
6.7 KiB
C
379 lines
6.7 KiB
C
/*
|
|
A thread interface implementation without any system thread.
|
|
|
|
Assumption:
|
|
* There is a only single thread in the ruby process
|
|
* No signal happens targeting the ruby process
|
|
|
|
Note:
|
|
* No thread switching in the VM
|
|
* No timer thread because thread switching won't happen
|
|
* No mutex guard because the VM won't be racy
|
|
*/
|
|
|
|
#ifdef THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION
|
|
|
|
#include <time.h>
|
|
|
|
#if defined(__wasm__) && !defined(__EMSCRIPTEN__)
|
|
# include "wasm/machine.h"
|
|
#endif
|
|
|
|
// Do nothing for GVL
|
|
static void
|
|
thread_sched_to_running(struct rb_thread_sched *sched, rb_thread_t *th)
|
|
{
|
|
}
|
|
|
|
static void
|
|
thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th, bool yield_immediately)
|
|
{
|
|
}
|
|
|
|
#define thread_sched_to_dead(a,b) thread_sched_to_waiting(a,b,true)
|
|
|
|
static void
|
|
thread_sched_yield(struct rb_thread_sched *sched, rb_thread_t *th)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_thread_sched_init(struct rb_thread_sched *sched, bool atfork)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_thread_sched_destroy(struct rb_thread_sched *sched)
|
|
{
|
|
}
|
|
|
|
// Do nothing for mutex guard
|
|
void
|
|
rb_native_mutex_lock(rb_nativethread_lock_t *lock)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
|
|
{
|
|
}
|
|
|
|
int
|
|
rb_native_mutex_trylock(rb_nativethread_lock_t *lock)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_initialize(rb_nativethread_cond_t *cond)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_destroy(rb_nativethread_cond_t *cond)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_signal(rb_nativethread_cond_t *cond)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_native_cond_timedwait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex, unsigned long msec)
|
|
{
|
|
}
|
|
|
|
// The only one thread in process
|
|
static rb_thread_t *ruby_native_thread;
|
|
|
|
rb_thread_t *
|
|
ruby_thread_from_native(void)
|
|
{
|
|
return ruby_native_thread;
|
|
}
|
|
|
|
int
|
|
ruby_thread_set_native(rb_thread_t *th)
|
|
{
|
|
if (th && th->ec) {
|
|
rb_ractor_set_current_ec(th->ractor, th->ec);
|
|
}
|
|
ruby_native_thread = th;
|
|
return 1; // always succeed
|
|
}
|
|
|
|
void
|
|
Init_native_thread(rb_thread_t *main_th)
|
|
{
|
|
// no TLS setup and no thread id setup
|
|
ruby_thread_set_native(main_th);
|
|
}
|
|
|
|
void
|
|
ruby_mn_threads_params(void)
|
|
{
|
|
}
|
|
|
|
static void
|
|
native_thread_destroy_atfork(struct rb_native_thread *nt)
|
|
{
|
|
/* no-op */
|
|
}
|
|
|
|
static int
|
|
native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
|
|
{
|
|
#if defined(__wasm__) && !defined(__EMSCRIPTEN__)
|
|
th->ec->machine.stack_start = (VALUE *)rb_wasm_stack_get_base();
|
|
#endif
|
|
return 0; // success
|
|
}
|
|
|
|
static int
|
|
native_thread_create(rb_thread_t *th)
|
|
{
|
|
th->status = THREAD_KILLED;
|
|
rb_ractor_living_threads_remove(th->ractor, th);
|
|
rb_notimplement();
|
|
}
|
|
|
|
// Do nothing for handling ubf because no other thread doesn't exist and unblock anything
|
|
#define register_ubf_list(th) (void)(th)
|
|
#define unregister_ubf_list(th) (void)(th)
|
|
#define ubf_select 0
|
|
|
|
inline static void
|
|
ubf_wakeup_all_threads(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
inline static int
|
|
ubf_threads_empty(void)
|
|
{
|
|
return 1; // true
|
|
}
|
|
|
|
inline static void
|
|
ubf_list_atfork()
|
|
{
|
|
}
|
|
|
|
inline static void
|
|
ubf_timer_disarm(void)
|
|
{
|
|
}
|
|
|
|
|
|
// No timer thread because thread switching won't happen
|
|
#define TIMER_THREAD_CREATED_P() (1)
|
|
inline static void
|
|
rb_thread_create_timer_thread(void)
|
|
{
|
|
}
|
|
|
|
void
|
|
rb_thread_wakeup_timer_thread(int sig)
|
|
{
|
|
}
|
|
|
|
inline static int
|
|
native_stop_timer_thread(void)
|
|
{
|
|
return 1; // success
|
|
}
|
|
|
|
inline static void
|
|
native_reset_timer_thread(void)
|
|
{
|
|
}
|
|
|
|
// Do nothing for thread naming
|
|
inline static void
|
|
native_set_thread_name(rb_thread_t *th)
|
|
{
|
|
}
|
|
|
|
inline static void
|
|
native_set_another_thread_name(rb_nativethread_id_t thread_id, VALUE name)
|
|
{
|
|
}
|
|
|
|
// Don't expose native thread id for now to keep system's thread API agnostic
|
|
#define USE_NATIVE_THREAD_NATIVE_THREAD_ID 0
|
|
|
|
// No reserved fd for piping threads
|
|
int
|
|
rb_reserved_fd_p(int fd)
|
|
{
|
|
return 0; // not reserved
|
|
}
|
|
|
|
// Don't expose native thread info for now to keep system's thread API agnostic
|
|
rb_nativethread_id_t
|
|
rb_nativethread_self(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Do nothing for sigwait things because of no signal assumption
|
|
// Q(katei): is this correct description?
|
|
int
|
|
rb_sigwait_fd_get(const rb_thread_t *th)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
NORETURN(void rb_sigwait_fd_put(rb_thread_t *, int));
|
|
void
|
|
rb_sigwait_fd_put(rb_thread_t *th, int fd)
|
|
{
|
|
rb_bug("not implemented, should not be called rb_sigwait_fd_put");
|
|
}
|
|
|
|
NORETURN(void rb_sigwait_sleep(const rb_thread_t *, int, const rb_hrtime_t *));
|
|
void
|
|
rb_sigwait_sleep(const rb_thread_t *th, int sigwait_fd, const rb_hrtime_t *rel)
|
|
{
|
|
rb_bug("not implemented, should not be called rb_sigwait_sleep");
|
|
}
|
|
|
|
static void
|
|
native_sleep(rb_thread_t *th, rb_hrtime_t *rel)
|
|
{
|
|
// No signal assumption allows the use of uninterruptible sleep
|
|
struct timespec ts;
|
|
(void)clock_nanosleep(CLOCK_REALTIME, 0, rb_hrtime2timespec(&ts, rel), NULL);
|
|
}
|
|
|
|
static int
|
|
native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout, rb_thread_t *th)
|
|
{
|
|
return rb_fd_select(n, readfds, writefds, exceptfds, timeout);
|
|
}
|
|
|
|
static bool
|
|
th_has_dedicated_nt(const rb_thread_t *th)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
rb_add_running_thread(rb_thread_t *th)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_del_running_thread(rb_thread_t *th)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_threadptr_sched_free(rb_thread_t *th)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_ractor_sched_barrier_end(rb_vm_t *vm, rb_ractor_t *cr)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ptr)
|
|
{
|
|
// nothing can wake this: with no threads there is nobody to send
|
|
rb_bug("unreachable");
|
|
}
|
|
|
|
void
|
|
rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *th)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
rb_thread_lock_native_thread(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void *
|
|
rb_thread_prevent_fork(void *(*func)(void *), void *data)
|
|
{
|
|
return func(data);
|
|
}
|
|
|
|
void
|
|
rb_thread_malloc_stack_set(rb_thread_t *th, void *stack, size_t stack_size)
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
bool
|
|
rb_thread_event_hooks_registered_p(void)
|
|
{
|
|
return false; // hooks are not implemented on this platform
|
|
}
|
|
|
|
#endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */
|
|
|
|
void
|
|
rb_thread_sched_winding_begin(rb_vm_t *vm)
|
|
{
|
|
// nothing to count: rb_thread_sched_wait_winding below never waits
|
|
(void)vm;
|
|
}
|
|
|
|
void
|
|
rb_thread_sched_winding_end(rb_vm_t *vm)
|
|
{
|
|
(void)vm;
|
|
}
|
|
|
|
void
|
|
rb_thread_sched_wait_winding(rb_vm_t *vm)
|
|
{
|
|
// no coroutine (M:N) threads on this implementation: nothing winds down
|
|
// after leaving the living set (see thread_pthread.c)
|
|
(void)vm;
|
|
}
|