From: ko1@... Date: 2020-09-07T01:49:51+00:00 Subject: [ruby-core:99958] [Ruby master Bug#17159] extend `define_method` for Ractor Issue #17159 has been reported by ko1 (Koichi Sasada). ---------------------------------------- Bug #17159: extend `define_method` for Ractor https://bugs.ruby-lang.org/issues/17159 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal * Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN ---------------------------------------- Ractor prohibits to use non-isolated Procs. Non-isolated example is here: ``` s = "foo" pr = Proc.new{ p s } ``` This Proc pr can not be shared with multi-ractors because outer variable `s` can contain an unshareable object. Also outer binding is mutable object so it can lead race condition. Because of these reasons, `define_method` are also problem on multi-Ractor program. (current implementation allows it just because check is not implemented, and it leads BUG). I think there are several patterns `define_method` are used. (1) want to choose variable method names ```ruby name = ... define_method(name){ nil } ``` (2) want to embed variables to the code ```ruby 10.times{|i| define_method("foo{i}"){ i } } ``` (3) want to use global state by local variables ```ruby cnt = 0 define_method("inc"){ cnt += 1 } ``` (4) others I can't imagine ---- (1) is easy. `define_method(name, &Proc{nil}.isoplate)` will be allowed on multi-ractors. (3) is not allowed because it introduces data races/race conditions. For example, we need to use shared hash. ```ruby STATE = SharedHash.new(cnt: 0) define_method("inc"){ STATE.transaction{ STATE[:cnt] += 1 }} ``` I think there are many (2) patterns and it should be saved. To help (2) pattern, the easiest way is to use eval. ```ruby 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 embed the current value to the code, like that: ```ruby 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 difference between current Proc semantics. Another idea is to specify embedding value like that. ```ruby 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 -- https://bugs.ruby-lang.org/ Unsubscribe: