Project

General

Profile

Bug #17159

extend `define_method` for Ractor

Added by ko1 (Koichi Sasada) 6 months ago. Updated 4 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:99958]

Description

Ractor prohibits use of non-isolated Procs.

Non-isolated example is here:

s = "foo"
pr = Proc.new{ p s }

This Proc pr can not be shared among ractors because outer variable s can contain an unshareable object. Also outer binding is a mutable object. Sharing it can lead race conditions.

Because of these reasons, define_method is also a problem on a multi-Ractor program.
(current implementation allows it just because check is not implemented, and it leads BUG).

I think there are several patterns when define_method is needed.

(1) To choose method names on-the-fly

name = ...
define_method(name){ nil }

(2) To embed variables to the code

10.times{|i|
  define_method("foo#{i}"){ i }
}

(3) To use global state by local variables

cnt = 0
define_method("inc"){ cnt += 1 }

(4) Others I can't imagine


(1) is easy. We can allow define_method(name, &Proc{nil}.isolate).

(3) can never be OK. It introduces data races/race conditions. For this purpose one need to use shared hash.

STATE = SharedHash.new(cnt: 0)
define_method("inc"){ STATE.transaction{ STATE[:cnt] += 1 }}

I think there are many (2) patterns that should be saved.
To help (2) pattern, the easiest way is to use eval.

10.times{|i|
  eval("def foo#{i} #{i}; end")
}

However, eval has several issues (it has huge freedom to explode the program, editor's syntax highlighting and so on).

Another approach is to embed the current value to the code, like this:

i = 0
define_method("foo", ractorise: true){ i }
#=> equivalent to:
#   define_method("foo"){ 0 }
# so that if outer scope's i changed, not affected.
i = 1
foo #=> 0

s = ""
define_method("bar", ractorise: true){ s }
#=> equivalent to:
#   define_method("bar"){ "" }
# so that if outer scope's s or s's value, it doesn't affect
s << "x"
bar #=> ""

However, it is very differenct from current Proc semantics.

Another idea is to specify embedding value like this:

i = 0
define_method("foo", i: i){ i }
#=> equivalent to:
#   define_method("foo"){ 0 }
# so that if outer scope's i changed, not affected.
i = 1
foo #=> 0

s = ""
define_method("bar", s: s){ s }
#=> equivalent to:
#   define_method("bar"){ "" }
# so that if outer scope's s or s's value, it doesn't affect
s << "x"
bar #=> ""

i: i and s: s are redundant. However, if there are no outer variable i or s, the i and s in blocks are compiled to send(:i) or send(:s). But I agree these method invocation should be replaced is another idea.

Thoughts?

Thanks,
Koichi


Related issues

Related to Ruby master - Feature #17100: Ractor: a proposal for a new concurrent abstraction without thread-safety issuesClosedko1 (Koichi Sasada)Actions

Also available in: Atom PDF