Skip to content

Commit 2e301ec

Browse files
Fix GC safety and write barriers in worker_pool.c
1. RB_GC_GUARD(_blocking_operation) in worker_pool_call The _blocking_operation VALUE argument is on the calling fiber's C stack. The GC finds it via conservative scan, marks the underlying TypedData object, and prevents collection — keeping the raw C pointer extracted before the fiber switch valid throughout the worker's execution. RB_GC_GUARD ensures the compiler doesn't treat the VALUE as dead before the end of the function. 2. RUBY_TYPED_WB_PROTECTED + compact function Without WB_PROTECTED, RB_OBJ_WRITE (used at worker creation) installed no write barrier. A new compact function updates worker->thread via rb_gc_location for proper compacting GC support. 3. rb_gc_mark_movable for thread objects Changed from rb_gc_mark (which pins) to rb_gc_mark_movable so threads can be relocated by the compacting GC. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 218d53a commit 2e301ec

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

ext/io/event/worker_pool.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,20 @@ static void worker_pool_mark(void *ptr)
9191
struct IO_Event_WorkerPool *pool = (struct IO_Event_WorkerPool *)ptr;
9292
struct IO_Event_WorkerPool_Worker *worker = pool->workers;
9393
while (worker) {
94-
struct IO_Event_WorkerPool_Worker *next = worker->next;
9594
// We need to mark the thread even though its marked through the VM's ractors because we call `join`
9695
// on them after their completion. They could be freed by then.
97-
rb_gc_mark(worker->thread); // thread objects are currently pinned anyway
98-
worker = next;
96+
rb_gc_mark_movable(worker->thread);
97+
worker = worker->next;
98+
}
99+
}
100+
101+
static void worker_pool_compact(void *ptr)
102+
{
103+
struct IO_Event_WorkerPool *pool = (struct IO_Event_WorkerPool *)ptr;
104+
struct IO_Event_WorkerPool_Worker *worker = pool->workers;
105+
while (worker) {
106+
worker->thread = rb_gc_location(worker->thread);
107+
worker = worker->next;
99108
}
100109
}
101110

@@ -107,8 +116,8 @@ static size_t worker_pool_size(const void *ptr) {
107116
// Ruby TypedData structures
108117
static const rb_data_type_t IO_Event_WorkerPool_type = {
109118
"IO::Event::WorkerPool",
110-
{worker_pool_mark, worker_pool_free, worker_pool_size,},
111-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
119+
{worker_pool_mark, worker_pool_free, worker_pool_size, worker_pool_compact},
120+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
112121
};
113122

114123
// Helper function to enqueue work (must be called with mutex held)
@@ -375,6 +384,8 @@ static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
375384

376385
if (DEBUG) fprintf(stderr, "<- worker_pool_call:work completed=%d, state=%d\n", work.completed, state);
377386

387+
RB_GC_GUARD(_blocking_operation);
388+
378389
if (state) {
379390
rb_jump_tag(state);
380391
} else {

0 commit comments

Comments
 (0)