Project

General

Profile

Bug #17159

Updated by nobu (Nobuyoshi Nakada) about 1 year ago

Ractor prohibits use of non-isolated `Proc`s. 

 Non-isolated example is here: 

 ```ruby 
 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 

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

 (2) To embed variables to the code 

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

 (3) To use global state by local variables 

 ```ruby 
 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)`. &Proc{nil}.isoplate)`. 

 (3) can never be OK. It introduces data races/race conditions. For this purpose one 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 that 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 to embed the current value to the code, like this: 

 ```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 differenct from current Proc semantics. 

 Another idea is to specify embedding value like this: 

 ```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

Back