Always allocate Fiber objects in Thread

Currently, root fibers of threads do not have a corresponding Ruby object
backing it by default (it does have one when an object is required, such
as when Fiber.current is called). This is a problem for the new GC weak
references design in #12606 since Thread is not declared as having weak
references but it does hold weak references (the generic ivar cache).

This commit changes it to always allocate a Fiber object for the root
fiber.
This commit is contained in:
Peter Zhu 2025-12-27 13:19:56 -05:00
parent 3fe2ebf8e4
commit eaa83e505f
Notes: git 2025-12-28 13:56:03 +00:00
2 changed files with 38 additions and 48 deletions

65
cont.c
View File

@ -953,7 +953,9 @@ fiber_verify(const rb_fiber_t *fiber)
switch (fiber->status) {
case FIBER_RESUMED:
VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL);
if (fiber->cont.saved_ec.thread_ptr->self == 0) {
VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL);
}
break;
case FIBER_SUSPENDED:
VM_ASSERT(fiber->cont.saved_ec.vm_stack != NULL);
@ -1141,12 +1143,7 @@ rb_fiber_update_self(rb_fiber_t *fiber)
void
rb_fiber_mark_self(const rb_fiber_t *fiber)
{
if (fiber->cont.self) {
rb_gc_mark_movable(fiber->cont.self);
}
else {
rb_execution_context_mark(&fiber->cont.saved_ec);
}
rb_gc_mark_movable(fiber->cont.self);
}
static void
@ -2067,32 +2064,10 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
return fiber;
}
static rb_fiber_t *
root_fiber_alloc(rb_thread_t *th)
{
VALUE fiber_value = fiber_alloc(rb_cFiber);
rb_fiber_t *fiber = th->ec->fiber_ptr;
VM_ASSERT(DATA_PTR(fiber_value) == NULL);
VM_ASSERT(fiber->cont.type == FIBER_CONTEXT);
VM_ASSERT(FIBER_RESUMED_P(fiber));
th->root_fiber = fiber;
DATA_PTR(fiber_value) = fiber;
fiber->cont.self = fiber_value;
coroutine_initialize_main(&fiber->context);
return fiber;
}
static inline rb_fiber_t*
fiber_current(void)
{
rb_execution_context_t *ec = GET_EC();
if (ec->fiber_ptr->cont.self == 0) {
root_fiber_alloc(rb_ec_thread_ptr(ec));
}
return ec->fiber_ptr;
}
@ -2598,6 +2573,7 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th)
if (!fiber) {
rb_bug("%s", strerror(errno)); /* ... is it possible to call rb_bug here? */
}
fiber->cont.type = FIBER_CONTEXT;
fiber->cont.saved_ec.fiber_ptr = fiber;
fiber->cont.saved_ec.serial = next_ec_serial(th->ractor);
@ -2605,10 +2581,23 @@ rb_threadptr_root_fiber_setup(rb_thread_t *th)
fiber->blocking = 1;
fiber->killed = 0;
fiber_status_set(fiber, FIBER_RESUMED); /* skip CREATED */
coroutine_initialize_main(&fiber->context);
th->ec = &fiber->cont.saved_ec;
cont_init_jit_cont(&fiber->cont);
}
void
rb_root_fiber_obj_setup(rb_thread_t *th)
{
rb_fiber_t *fiber = th->ec->fiber_ptr;
VALUE fiber_value = fiber_alloc(rb_cFiber);
DATA_PTR(fiber_value) = fiber;
fiber->cont.self = fiber_value;
}
void
rb_threadptr_root_fiber_release(rb_thread_t *th)
{
@ -2679,15 +2668,7 @@ rb_fiber_current(void)
static inline void
fiber_store(rb_fiber_t *next_fiber, rb_thread_t *th)
{
rb_fiber_t *fiber;
if (th->ec->fiber_ptr != NULL) {
fiber = th->ec->fiber_ptr;
}
else {
/* create root fiber */
fiber = root_fiber_alloc(th);
}
rb_fiber_t *fiber = th->ec->fiber_ptr;
if (FIBER_CREATED_P(next_fiber)) {
fiber_prepare_stack(next_fiber);
@ -2723,7 +2704,9 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi
rb_thread_t *th = GET_THREAD();
/* make sure the root_fiber object is available */
if (th->root_fiber == NULL) root_fiber_alloc(th);
if (th->root_fiber == NULL) {
th->root_fiber = th->ec->fiber_ptr;
}
if (th->ec->fiber_ptr == fiber) {
/* ignore fiber context switch
@ -3558,6 +3541,10 @@ Init_Cont(void)
rb_define_singleton_method(rb_cFiber, "schedule", rb_fiber_s_schedule, -1);
rb_thread_t *current_thread = rb_current_thread();
RUBY_ASSERT(CLASS_OF(current_thread->ec->fiber_ptr->cont.self) == 0);
*(VALUE *)&((struct RBasic *)current_thread->ec->fiber_ptr->cont.self)->klass = rb_cFiber;
#ifdef RB_EXPERIMENTAL_FIBER_POOL
/*
* Document-class: Fiber::Pool

21
vm.c
View File

@ -3738,6 +3738,7 @@ rb_execution_context_mark(const rb_execution_context_t *ec)
void rb_fiber_mark_self(rb_fiber_t *fib);
void rb_fiber_update_self(rb_fiber_t *fib);
void rb_threadptr_root_fiber_setup(rb_thread_t *th);
void rb_root_fiber_obj_setup(rb_thread_t *th);
void rb_threadptr_root_fiber_release(rb_thread_t *th);
static void
@ -3746,10 +3747,6 @@ thread_compact(void *ptr)
rb_thread_t *th = ptr;
th->self = rb_gc_location(th->self);
if (!th->root_fiber) {
rb_execution_context_update(th->ec);
}
}
static void
@ -3757,7 +3754,11 @@ thread_mark(void *ptr)
{
rb_thread_t *th = ptr;
RUBY_MARK_ENTER("thread");
rb_fiber_mark_self(th->ec->fiber_ptr);
// ec is null when setting up the thread in rb_threadptr_root_fiber_setup
if (th->ec) {
rb_fiber_mark_self(th->ec->fiber_ptr);
}
/* mark ruby objects */
switch (th->invoke_type) {
@ -3813,8 +3814,6 @@ thread_free(void *ptr)
ruby_xfree(th->specific_storage);
rb_threadptr_root_fiber_release(th);
if (th->vm && th->vm->ractor.main_thread == th) {
RUBY_GC_INFO("MRI main thread\n");
}
@ -3917,6 +3916,8 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm)
th->self = self;
ccan_list_head_init(&th->interrupt_exec_tasks);
rb_threadptr_root_fiber_setup(th);
/* All threads are blocking until a non-blocking fiber is scheduled */
@ -3961,8 +3962,6 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm)
th->report_on_exception = vm->thread_report_on_exception;
th->ext_config.ractor_safe = true;
ccan_list_head_init(&th->interrupt_exec_tasks);
#if USE_RUBY_DEBUG_LOG
static rb_atomic_t thread_serial = 1;
th->serial = RUBY_ATOMIC_FETCH_ADD(thread_serial, 1);
@ -3978,6 +3977,7 @@ rb_thread_alloc(VALUE klass)
rb_thread_t *target_th = rb_thread_ptr(self);
target_th->ractor = GET_RACTOR();
th_init(target_th, self, target_th->vm = GET_VM());
rb_root_fiber_obj_setup(target_th);
return self;
}
@ -4525,6 +4525,8 @@ Init_VM(void)
th->top_wrapper = 0;
th->top_self = rb_vm_top_self();
rb_root_fiber_obj_setup(th);
rb_vm_register_global_object((VALUE)iseq);
th->ec->cfp->iseq = iseq;
th->ec->cfp->pc = ISEQ_BODY(iseq)->iseq_encoded;
@ -4599,6 +4601,7 @@ Init_BareVM(void)
th_init(th, 0, vm);
rb_ractor_set_current_ec(th->ractor, th->ec);
/* n.b. native_main_thread_stack_top is set by the INIT_STACK macro */
ruby_thread_init_stack(th, native_main_thread_stack_top);