Project

General

Profile

Actions

Bug #22521

open

`Ractor.shareable_proc` allows outer variables reassigned by loops, and silently keeps a stale value

Bug #22521: `Ractor.shareable_proc` allows outer variables reassigned by loops, and silently keeps a stale value

Added by Eregon (Benoit Daloze) about 21 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:127381]

Description

Ractor.shareable_proc must reject blocks reading an outer variable which may be reassigned, because the shareable Proc copies the variable's value ([Feature #21557]). The "Loop Cases" section of #21557 lists cases which should be Ractor::IsolationError but are currently allowed, because the variable is assigned only once in the source:

# for loop: one variable, reassigned on each iteration
procs = []
for x in [1, 2]
  procs << Ractor.shareable_proc { x }
end
p procs.map(&:call) # Ruby block semantics: [2, 2]

# while loop
procs = []
i = 0
while i < 2
  y = i
  procs << Ractor.shareable_proc { y }
  i += 1
end
p procs.map(&:call) # [1, 1]

# begin/rescue/retry
procs = []
i = 0
begin
  z = i
  procs << Ractor.shareable_proc { z }
  raise if (i += 1) < 2
rescue
  retry
end
p procs.map(&:call) # [1, 1]

# redo in a block
procs = []
i = 0
[1].each do
  w = i
  procs << Ractor.shareable_proc { w }
  redo if (i += 1) < 2
end
p procs.map(&:call) # [1, 1]
$ ruby -v loops.rb
ruby 4.1.0dev (2026-11-09T15:47:20Z master 7e5d882817) +PRISM [arm64-darwin25]
[1, 2]
[0, 1]
[0, 1]
[0, 1]

Same result on 4.0.7. With proc { x } instead of Ractor.shareable_proc { x }, the first case gives [2, 2].

Each shareable Proc silently keeps the value the variable had when it was created and ignores the later reassignments. This is exactly what #21557 wanted to prevent by raising Ractor::IsolationError. Since #22444 this also applies to Ractor.new blocks.

Cause

The static analysis marks a variable as reassigned only if the source contains more than one assignment to it. It does not consider that a single assignment can be executed several times for the same variable:

  • in a while/until loop (including its condition),
  • in a begin body whose rescue uses retry,
  • in a block using redo, which reruns the body in the same frame,
  • from a nested block, which may be called many times. This covers for loops, whose variables belong to the enclosing scope and are assigned from the loop's block.

Fix

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

The fix treats such assignments as reassignments, in both prism_compile.c and compile.c. Assignments from rescue/ensure clauses are also level > 0 and are conservatively treated the same way. Blocks still get a new frame per call, so this keeps working:

[1, 2].each do |e|
  f = e * 2
  Ractor.shareable_proc { [e, f] } # OK
end

With the fix, the cases above raise:

cannot make a shareable Proc because the outer variable 'x' may be reassigned. (Ractor::IsolationError)

Code which currently creates such a shareable Proc gets Ractor::IsolationError after this fix, but it is currently silently getting stale values, so I don't think this should be backported.


Related issues 1 (0 open — 1 closed)

Related to Ruby - Feature #21557: Ractor.shareable_proc to make sharable Proc objects, safely and flexiblyClosedko1 (Koichi Sasada)Actions
Actions

Also available in: PDF Atom