From 7de31cd00d94d3989efb3fe732bfaa8409e97737 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 12 May 2026 11:08:25 +0900 Subject: [PATCH] Improve GC compaction support and write barriers in WorkerPool Three corrections to make WorkerPool a better citizen of the compacting and generational GC: * Add `worker_pool_compact` so the compacting GC can relocate worker thread VALUEs (via `rb_gc_location`). * Mark `worker->thread` with `rb_gc_mark_movable` rather than `rb_gc_mark`, so Thread objects are no longer pinned. * Add `RUBY_TYPED_WB_PROTECTED` so the existing `RB_OBJ_WRITE` at worker creation actually installs the write barrier. These are correctness improvements; they do not fix the use-after-free addressed separately in #174. Co-authored-by: Cursor --- ext/io/event/worker_pool.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ext/io/event/worker_pool.c b/ext/io/event/worker_pool.c index fad4a40f..71233c4e 100644 --- a/ext/io/event/worker_pool.c +++ b/ext/io/event/worker_pool.c @@ -91,11 +91,20 @@ static void worker_pool_mark(void *ptr) struct IO_Event_WorkerPool *pool = (struct IO_Event_WorkerPool *)ptr; struct IO_Event_WorkerPool_Worker *worker = pool->workers; while (worker) { - struct IO_Event_WorkerPool_Worker *next = worker->next; // We need to mark the thread even though its marked through the VM's ractors because we call `join` // on them after their completion. They could be freed by then. - rb_gc_mark(worker->thread); // thread objects are currently pinned anyway - worker = next; + rb_gc_mark_movable(worker->thread); + worker = worker->next; + } +} + +static void worker_pool_compact(void *ptr) +{ + struct IO_Event_WorkerPool *pool = (struct IO_Event_WorkerPool *)ptr; + struct IO_Event_WorkerPool_Worker *worker = pool->workers; + while (worker) { + worker->thread = rb_gc_location(worker->thread); + worker = worker->next; } } @@ -107,8 +116,8 @@ static size_t worker_pool_size(const void *ptr) { // Ruby TypedData structures static const rb_data_type_t IO_Event_WorkerPool_type = { "IO::Event::WorkerPool", - {worker_pool_mark, worker_pool_free, worker_pool_size,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY + {worker_pool_mark, worker_pool_free, worker_pool_size, worker_pool_compact}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; // Helper function to enqueue work (must be called with mutex held)