Skip to content

Commit f21bc22

Browse files
Fix: keep blocking_operation_value as explicit GC root for worker lifetime
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f287187 commit f21bc22

1 file changed

Lines changed: 26 additions & 8 deletions

File tree

ext/io/event/worker_pool.c

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include <string.h>
1616

1717
enum {
18-
DEBUG = 0,
18+
DEBUG = 1,
1919
};
2020

2121
static VALUE IO_Event_WorkerPool;
@@ -37,7 +37,12 @@ struct IO_Event_WorkerPool_Worker {
3737

3838
// Work item structure
3939
struct IO_Event_WorkerPool_Work {
40+
// The raw C pointer is used by the worker thread (no GVL required).
41+
// blocking_operation_value is the Ruby VALUE that owns this pointer —
42+
// it must stay alive for the full lifetime of the operation so that
43+
// the embedded state struct the worker writes into is not freed by GC.
4044
rb_fiber_scheduler_blocking_operation_t *blocking_operation;
45+
VALUE blocking_operation_value;
4146

4247
bool completed;
4348

@@ -147,7 +152,7 @@ static void worker_unblock_func(void *_worker) {
147152

148153
// If there's a currently executing blocking operation, cancel it
149154
if (worker->current_blocking_operation) {
150-
fprintf(stderr, "[worker_pool] unblock: cancelling blocking_operation=%p\n",
155+
if (DEBUG) fprintf(stderr, "worker_unblock_func: cancelling blocking_operation=%p\n",
151156
(void*)worker->current_blocking_operation);
152157
rb_fiber_scheduler_blocking_operation_cancel(worker->current_blocking_operation);
153158
}
@@ -179,7 +184,7 @@ static void *worker_wait_and_execute(void *_worker) {
179184

180185
// Execute work WITHOUT GVL (this is the whole point!)
181186
if (work) {
182-
fprintf(stderr, "[worker_pool] execute: blocking_operation=%p\n",
187+
if (DEBUG) fprintf(stderr, "worker_wait_and_execute: blocking_operation=%p\n",
183188
(void*)work->blocking_operation);
184189
worker->current_blocking_operation = work->blocking_operation;
185190
rb_fiber_scheduler_blocking_operation_execute(work->blocking_operation);
@@ -336,15 +341,25 @@ static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
336341
rb_raise(rb_eArgError, "Invalid blocking operation!");
337342
}
338343

339-
// Create work item
344+
// Create work item.
345+
// Keep blocking_operation_value as an explicit GC root: the raw C pointer
346+
// is used by the worker thread without the GVL, and if an exception causes
347+
// the caller's Ruby frames to be unwound, nothing else will keep the VALUE
348+
// alive — which would allow GC to free the embedded state the worker is
349+
// still writing into.
340350
struct IO_Event_WorkerPool_Work work = {
341351
.blocking_operation = blocking_operation,
352+
.blocking_operation_value = _blocking_operation,
342353
.completed = false,
343354
.scheduler = scheduler,
344355
.blocker = self,
345356
.fiber = fiber,
346357
.next = NULL
347358
};
359+
rb_gc_register_address(&work.blocking_operation_value);
360+
rb_gc_register_address(&work.scheduler);
361+
rb_gc_register_address(&work.blocker);
362+
rb_gc_register_address(&work.fiber);
348363

349364
// Enqueue work:
350365
pthread_mutex_lock(&pool->mutex);
@@ -379,11 +394,14 @@ static VALUE worker_pool_call(VALUE self, VALUE _blocking_operation) {
379394

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

397+
// Unregister GC roots before returning or re-raising.
398+
rb_gc_unregister_address(&work.blocking_operation_value);
399+
rb_gc_unregister_address(&work.scheduler);
400+
rb_gc_unregister_address(&work.blocker);
401+
rb_gc_unregister_address(&work.fiber);
402+
382403
if (state) {
383-
// Exception path: cleanup in rb_fiber_scheduler_blocking_operation_wait is
384-
// bypassed (operation->state becomes a dangling pointer to our caller's stack).
385-
// Log the blocking_operation so we can correlate with any later cancel calls.
386-
fprintf(stderr, "[worker_pool] exception path: blocking_operation=%p state_tag=%d\n",
404+
if (DEBUG) fprintf(stderr, "[worker_pool] exception path: blocking_operation=%p state_tag=%d\n",
387405
(void*)blocking_operation, state);
388406
rb_jump_tag(state);
389407
} else {

0 commit comments

Comments
 (0)