Project

General

Profile

Actions

Feature #21550

closed

Ractor.shareable_proc/shareable_lambda to make sharable Proc object

Added by ko1 (Koichi Sasada) about 1 month ago. Updated 10 days ago.

Status:
Closed
Target version:
[ruby-core:123042]

Description

Let's introduce a way to make a sharable Proc.

  • Ractor.shareable_proc(self: nil, &block) makes proc.
  • Ractor.shareable_lambda(self: nil, &block) makes lambda.

See also: https://bugs.ruby-lang.org/issues/21039

Background

Motivation

Being able to create a shareable Proc is important for Ractors. For example, we often want to send a task to another Ractor:

worker = Ractor.new do
  while task = Ractor.receive
    task.call(...)
  end
end

task = (shareable_proc)
worker << task

task = (shareable_proc)
worker << task

task = (shareable_proc)
worker << task

There are various ways to represent a task, but using a Proc is straightforward.

However, to make a Proc shareable today, self must also be shareable, which leads to patterns like:

  nil.instance_eval{ Proc.new{ ... } }

This is noisy and cryptic. We propose dedicated methods to create shareable Proc objects directly.

Specification

  • Ractor.shareable_proc(self: nil, &block) makes a proc.
  • Ractor.shareable_lambda(self: nil, &block) makes a lambda.

Both methods create the Proc/lambda with the given self and make the resulting object shareable.

(changed) Accessing outer variables are not allowed. An error is raised at the creation.

More about outer-variable handling are discussed below.

In other words, from the perspective of a shareable Proc, captured outer locals are read‑only constants.

This proposal does not change the semantics of Ractor.make_shareable() itself.

Discussion about outer local variables

[Feature #21039] discusses how captured variables should be handled.
I propose two options.

0. Disallow accessing to the outer-variables

It is simple and no confusion.

1. No problem to change the outer-variable semantics

@Eregon (Benoit Daloze) noted that the current behavior of Ractor.make_shareable(proc_obj) can surprise users. While that is understandable, Ruby already has similar surprises.

For instance:

RSpec.describe 'foo' do
  p self #=> RSpec::ExampleGroups::Foo
end

Here, self is implicitly replaced, likely via instance_exec.
This can be surprising if one does not know self can change, yet it is accepted in Ruby.
We view the current situation as a similar kind of surprise.

2. Enforce a strict rule for non‑lexical usage

The difficulty is that it is hard to know which block will become shareable unless it is lexically usage.

# (1) On this code, it is clear that the block will be shareable block:

a = 42
Ractor.shareable_proc{
  p a
}

# (2) On this code, it is not clear that the block becomes sharable or not
get path do
  p a
end

# (3) On this code, it has no problem because
get '/hello' do
  "world"
end

The idea is to allow accessing captured outer variables only for lexically explicit uses of Ractor.shareable_proc as in (1), and to raise an error for non‑lexical cases as in (2).
So the example (3) is allowed if the block becomes sharable or not.

The strict rule is same as Ractor.new block rule.

3. Adding new rules

(quoted from https://bugs.ruby-lang.org/issues/21550#note-7)

Returning to the issue: we want a way to express that, within a block, an outer variable is shadowed while preserving its current value.

We already have syntax to shadow an outer variable using |i; a|, where a is shadowed in the block and initialized to nil (just like a normal local variable).

a = 42
pr = proc{|;a| p a}
a = 43
pr.call #=> nil

What if we instead initialized the shadowed variable to the outer variable's current value?

a = 42
pr = proc{|;a| p a}
a = 43
pr.call #=> 42

For example, we can write the port example like that:


port = Ractor::Port.new
Ractor.new do |;port|
  port << ...
end

and it is better (shorter).

Maybe only few people know this spec and I checked that there are few lines in rubygems (78 cases in 3M files)(*1).
So I think there is a few compatibility impact.


Related issues 2 (0 open2 closed)

Related to Ruby - Feature #21039: Ractor.make_shareable breaks block semantics (seeing updated captured variables) of existing blocksClosedko1 (Koichi Sasada)Actions
Related to Ruby - Feature #21557: Ractor.shareable_proc to make sharable Proc objects, safely and flexiblyClosedko1 (Koichi Sasada)Actions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0