Project

General

Profile

Actions

Bug #22525

open

Ractor.shareable_proc sees nil for a block parameter of the method which was not read yet

Bug #22525: Ractor.shareable_proc sees nil for a block parameter of the method which was not read yet

Added by Eregon (Benoit Daloze) about 18 hours ago.

Status:
Open
Assignee:
-
Target version:
ruby -v:
ruby 4.1.0dev (2026-11-09T15:47:20Z master 7e5d882817) +PRISM [arm64-darwin25]
[ruby-core:127387]

Description

When a shareable Proc reads a block parameter (&b) of the enclosing method, it gets nil if the method did not read b before. It does not raise Ractor::IsolationError even when the block is not shareable, and it loses the block even when the block is shareable:

def read_block(&b) = Ractor.shareable_proc { b }

p read_block { 1 }.call # expected: Ractor::IsolationError, as the block is not shareable

shareable = Ractor.shareable_proc { 2 }
p read_block(&shareable).call # expected: the shareable Proc
$ ruby -v blockparam.rb
ruby 4.1.0dev (2026-11-09T15:47:20Z master 7e5d882817) +PRISM [arm64-darwin25]
nil
nil

Same result on 4.0.7. Since #22444 this also applies to Ractor.new blocks.

Cause

The block parameter of a method is only set when it is first read: getblockparam (and getblockparamproxy for b.call) creates the Proc from the block handler of the method's frame and stores it in the local variable slot. Until then, the slot holds nil.

When making a Proc shareable, env_copy copies the slot, which is still nil. The copied environment has no block handler, so reading b in the shareable Proc gives nil.

Binding#local_variable_get already handles this case (get_local_variable_ptr in proc.c) by setting the block parameter first.

Additionally, b.call, b[], b.yield and b === x are compiled to getblockparamproxy without recording b as an outer variable of the block, so for these b is not checked nor copied at all:

def call_block(&b) = Ractor.shareable_proc { b.call }
call_block { 1 } # no error, the shareable Proc raises NoMethodError when called

Fix

PR: https://github.com/ruby/ruby/pull/19808

The fix does the same in env_copy: it sets the block parameter before checking whether its value is shareable and copying it. It also marks the block parameter as set in the copied environment, so that the shareable Proc reads the copied value. It also records b as an outer variable for b.call and the like, in both prism_compile.c and compile.c.

This also matters for https://github.com/ruby/ruby/pull/19801 (making the body of define_method shareable when possible). With that change, this pattern from rspec-support (and similar code in ActiveRecord) got nil instead of the block:

def self.define_optimized_require_for_rspec(lib, &require_relative)
  define_singleton_method("require_rspec_#{lib}") do |f|
    require_relative.call("#{lib}/#{f}")
  end
end

With the fix, such a body is not made shareable, because the block is not shareable, and the method works as before.

No data to display

Actions

Also available in: PDF Atom