1515#include <string.h>
1616
1717enum {
18- DEBUG = 0 ,
18+ DEBUG = 1 ,
1919};
2020
2121static VALUE IO_Event_WorkerPool ;
@@ -37,7 +37,12 @@ struct IO_Event_WorkerPool_Worker {
3737
3838// Work item structure
3939struct 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