Project

General

Profile

Feature #17274

Updated by ko1 (Koichi Sasada) over 3 years ago

This ticket describes the semantics of "shareable" and proposes a new method `Ractor.make_shareable(obj)`. 
 With this method, `obj` becomes a shareable object by freezing it and reachable objects if it is necessary and it is possible. 

 ## Background 

 "Shareable object" is new term used by Ractors. 

 * (1) We can send a reference to send a shareable object instead of doing deep copy. 
 * (2) We can access to a constant which contains a shareable object from non-main ractors. 

 (1) is (mainly) performance and (2) is programmability (how to rewrite the libraries and so on). See [Feature #1727] for the examples of (2). 

 The definition of shareable object is thread-safe, ractor-safe object, they are safe to access from multiple ractors simultaneously. 

 The following conditions are definition of "shareable object" (`obj` is shareable object if ...). 

 * SPECIAL_CONST objects are shareable (also be frozen). 
 * if `RBASIC(obj)->flags | FL_SHAREABLE` is true, it is shareable. 
 * T_OBJECT: if all instance variables only refer to shareable objects (def1) and itself is frozen (def2) 
 * T_ARRAY: (def1) + (def2) + if all elements are shareable objects 
 * T_HASH: (def1) + (def2) + if all keys and values are sharable objects and default_proc/value (IFNONE, in C-level) is a sharable object 
 * T_STRUCT: (def1) + (def2) + if all members are shareable objects 
 * T_RATIONAL: (def1) + (def2) + if num/den are shareaable 
 * T_COMPLEXL: (def1) + (def2) + if imag/real are shareable  
 * T_STRING, T_FILE, T_MATCH, T_REGEXP: (def1) + (def2) 

 `T_DATA` (user customizable data structure) is difficult problem because if it is frozen, it can modify a state (== we can use (def2)), for example current Queue implementation ignores frozen flag. So we define the semantics like: 

 * `T_DATA`: (def1) + if `RTYPEDDATA_P(obj)` is true and `rb_data_type_t::flags | RUBY_TYPED_FROZEN_SHAREABLE`, we rely on (def2). Otherwize, this T_DATA object can not become a shareable object. Also we need to check reachable objects are shareable. 

 `Ractor.shareable?(obj)` checks this definitions. 

 Note that you can add `FL_SHAREABLE` flag to any objects, so if you know there is no mutation or enough protected, you can set the flag and it will be a shareable object. For example, [Feature #17261] use this flag and `TVar`s are shareable objects. 

 ## Proposal 

 As you can see, most of objects are shareable if they are frozen and they are only refers shareable/frozen objects. reference (deeply). 
 `Ractor.make_shareable(obj)` tries to freeze objects recursively if it is non-shareable objects. 

 ```ruby 
 # puseudo-code 

 def Ractor.make_shareable(obj) 
   return obj if Ractor.shareable(obj) 

   obj.freeze 

   if obj.is T_DATA and (obj.type.flags | RUBY_TYPED_FROZEN_SHAREABLE) == 0 
     raise "can not make shareable object for ..." 
   end 

   obj.reachable_objects{|o| 
     Ractor.make_shareable(o) 
   } 

   # only refer to the shareable objects, so it can be a shareable. 
   obj.set! FL_SHAREBLE 
 end 
 ``` 

 If it raises an error in the middle of the process, half-baked state are remained. 

 ``` 
 begin 
   Ractor.make_shareable [ a1 = [1, 2], 
                           Thread.new{}, 
                           a2 = [3, 4]] 
 rescue Ractor::Error 
 end 

 p Ractor.shareable?(a1) #=> true 
 p Ractor.shareable?(a2) #=> false 
 ``` 

 ## Implementation 

 https://github.com/ruby/ruby/pull/3678 
 and it was already merged to propose https://bugs.ruby-lang.org/issues/17273 

Back