@@ -1088,6 +1088,15 @@ rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname)
10881088 * Thread.new { blocking_operation.call }.join
10891089 * end
10901090 */
1091+ // Helper for rb_protect: calls scheduler.blocking_operation_wait(blocking_operation).
1092+ // args[0] = scheduler VALUE, args[1] = blocking_operation VALUE.
1093+ static VALUE
1094+ scheduler_blocking_operation_wait_call (VALUE _args )
1095+ {
1096+ VALUE * args = (VALUE * )_args ;
1097+ return rb_funcall (args [0 ], id_blocking_operation_wait , 1 , args [1 ]);
1098+ }
1099+
10911100VALUE rb_fiber_scheduler_blocking_operation_wait (VALUE scheduler , void * (* function )(void * ), void * data , rb_unblock_function_t * unblock_function , void * data2 , int flags , struct rb_fiber_scheduler_blocking_operation_state * state )
10921101{
10931102 // Check if scheduler supports blocking_operation_wait before creating the object
@@ -1098,25 +1107,35 @@ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*functi
10981107 // Create a new BlockingOperation with the blocking operation
10991108 VALUE blocking_operation = rb_fiber_scheduler_blocking_operation_new (function , data , unblock_function , data2 , flags , state );
11001109
1101- rb_fiber_scheduler_blocking_operation_t * operation = NULL ; // get_blocking_operation(blocking_operation);
1110+ rb_fiber_scheduler_blocking_operation_t * operation = get_blocking_operation (blocking_operation );
11021111
1103- VALUE result = rb_funcall (scheduler , id_blocking_operation_wait , 1 , blocking_operation );
1112+ // Use rb_protect so that cleanup runs even when the scheduler raises an exception
1113+ // (e.g. via rb_jump_tag from worker_pool_call). Without this, a longjmp from
1114+ // inside rb_funcall bypasses the cleanup below and RB_GC_GUARD, leaving
1115+ // operation with stale pointers and blocking_operation without a live GC root.
1116+ VALUE call_args [2 ] = {scheduler , blocking_operation };
1117+ int tag = 0 ;
1118+ VALUE result = rb_protect (scheduler_blocking_operation_wait_call , (VALUE )call_args , & tag );
11041119
11051120 operation = get_blocking_operation (blocking_operation );
11061121
11071122 // Get the operation data to check if it was executed
11081123 rb_atomic_t current_status = RUBY_ATOMIC_LOAD (operation -> status );
11091124
1110- // Invalidate the operation now that we're done with it
1125+ // Invalidate the operation now that we're done with it — must happen even on
1126+ // exception paths, since operation->state may point to a caller's stack frame.
11111127 operation -> function = NULL ;
11121128 operation -> state = NULL ;
11131129 operation -> data = NULL ;
11141130 operation -> data2 = NULL ;
11151131 operation -> unblock_function = NULL ;
11161132
1117- // Ensure that the blocking operation remains visible until this point:
1133+ // Ensure that blocking_operation remains a live GC root through the cleanup above.
11181134 RB_GC_GUARD (blocking_operation );
11191135
1136+ // Re-raise any exception from the scheduler after cleanup.
1137+ if (tag ) rb_jump_tag (tag );
1138+
11201139 // If the blocking operation was never executed, return Qundef to signal the caller to use rb_nogvl instead
11211140 if (current_status == RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED ) {
11221141 return Qundef ;
0 commit comments