Project

General

Profile

Feature #18926

Updated by Eregon (Benoit Daloze) almost 2 years ago

This is an improvement suggestion in order to foster adoption of ractors. It may not be technically impossible or unfeasible for some reason, as it may lead to deadlocks, so feel free to discard it if massively hard to undertake. 

 There's a pattern, common to a lot of popular gems, and stdlib, in the wild, which I call "lazy-load-then-cache". Essentially, smth like: 

 ```ruby 
 class A 
   @map = {} 
   @map_mutex = Mutex.new 

   def self.set_in_map(name, value) 
     @map_mutex.synchronize do 
       @map[name] = value     
     end 
   end 

   def self.get_from_map(name) 
     if not @map.key?(name) 
       value = do_smth_heavy_to_figure_out(name) 
       set_in_map(name, value) 
       value 
     else 
       @map[name] MAP[name] 
     end 
   end 
 end 
 ``` 

 The main issues here regarding ractor safety are: 

 * `@map` MAP is not frozen 
 * ractor does not support usage of mutexes 

 Examples: 

 * sequel: https://github.com/jeremyevans/sequel/blob/master/lib/sequel/database/connecting.rb#L76 
 * resolv:  
   * https://github.com/ruby/resolv/blob/master/lib/resolv.rb#L187 (instance-based variation of the above) 
   * https://github.com/ruby/resolv/blob/master/lib/resolv.rb#L1341 (not protected by mutex, but still a usage of a global counter) 

 While I've found [a gem implementing a ractor-aware cache](https://github.com/ractor-tools/ractor-cache), while looking a bit outdated, it also makes use of `ObjectSpace::WeakMap`, which is probably a dealbreaker and a "hack" around ractor's limitations. It's also not necessarily a "drop-in" replacement for the pattern exemplified above. 

 ---- 

 Theoretically, ractor could support the pattern above, by allowing the usage of mutexes. It should however run mutex blocks exclusively across ractors, while also disabling "ractor checks". This means that a mutable `@map` MAP could be mutated within a ractor, as long as protected by a mutex. However, it should be marked as frozen before the block terminates. So the code above should be modified to: 

 ```ruby 
 @map = {}.freeze 
 @map_mutex = Mutex.new 

 def self.set_in_map(name, value) 
   @map_mutex.synchronize do 
     @map = @map.merge(name => value) 
     @map.freeze 
   end 
 end 
 ``` 


 ---- 

 Again, there may be implementation limitations not enabling usage of such a pattern. But it's a telling sign when ractors can't support simple usages of stdlib. So this proposal aims at enabling yet another case which may diminish the overhead of supporting ractors going forward, thereby making ractors usable in more situations.

Back