Feature #19472
openRactor::Selector to wait multiple ractors
Description
This ticket propose Ractor::Selector API to wait multiple ractor events.
Now, if we want to wait for taking from r1, r2 and r3, we can use Ractor.select() like that.
r, v = Ractor.select(r1, r2, r3)
p "taking an object #{v} from #{r}"
With proposed Ractor::Selector API, we can write the following:
selector = Ractor::Selector.new(r1, r2) # make a waiting set with r1 and r2
selector.add(r3) # we can add r3 to the waiting set after that.
selector.add(r4)
selector.remove(r4) # we can remove r4 from the waiting set.
r, v = selector.wait
p "taking an object #{v} from #{r}"
- 
Ractor::Selector.new(*ractors): creates a selector.
- 
Ractor::Selector#add(r): addsrto the waiting set.
- 
Ractor::Selector#remove(r): removesrfrom the waiting set.
- 
Ractor::Selector#clear: remove all ractors from the waiting set.
- 
Ractor::Selector#empty?: returns if the waiting set is empty or not.
- 
Ractor::Selector#wait: waits for the ractor events from the waiting set.
https://github.com/ruby/ruby/blob/master/ractor.rb#L380
The advantages comparing with Ractor.select are:
- (1) (API design) We can preset the waiting set before waiting. Providing unified way to manage a waiting set seems better.
- (2) (Performance) It is lighter than passing an array object to the Ractor.select(*rs)ifrsis bigger and bigger.
For (2), it is important to supervise thousands of ractors.
Ractor::Selector#wait also has additional features:
- 
wait(receive: true)also waits receiving.- 
Ractor.select(*rs, Ractor.current)does same, but I believereceive: truekeyword is more direct to understand.
 
- 
- 
wait(yield_value: obj, move: true/false)also waits yielding.- Same as Ractor.select(yield_value: obj, move: true/false)
 
- Same as 
- If a ractor ris closing, then#waitremovesrautomatically.
- If there is no waiting ractors, it raises an exception (now Ractor::Erroris raised but it should be a better exception class)
With automatic removing, we can write the code to wait n tasks.
rs = n.times.map{ Ractor.new{ do_task } }
selector = Ractor::Selector.new(*rs)
loop do
  r, v = selector.wait
  handle_answers(r, v)
rescue Ractor::Error
  p :all_tasks_done
end
Without auto removing, we can write the following code.
rs = n.times.map{ Ractor.new{ do_task } }
selector = Ractor::Selector.new(*rs)
loop do
  r, v = selector.wait
  handle_answers(r, v)
rescue Ractor::ClosedError => e
  selector.remove e.ractor
rescue Ractor::Error
  p :all_tasks_done
end
# or on this case worker ractors only yield one value (at exit) so the following code works as well.
loop do
  r, v = selector.wait
  handle_answers(r, v)
  selector.remove r
rescue Ractor::Error
  p :all_tasks_done
end
I already merged it but I want to discuss about the spec.
Discussion:
- The name Selectoris acceptable?
- Auto-removing seems convenient but it can hide the behavior.
- allow auto-removing
- allow auto-removing as configurable option
- per ractor or per selector
- which is default?
 
- disallow auto-removing
 
- What happens on no taking ractors
- raise an exception (which exception?)
- return nil simply
 
maybe and more...
        
           Updated by ko1 (Koichi Sasada) over 2 years ago
          Updated by ko1 (Koichi Sasada) over 2 years ago
          
          
        
        
      
      - Description updated (diff)
        
           Updated by ioquatix (Samuel Williams) over 2 years ago
          Updated by ioquatix (Samuel Williams) over 2 years ago
          
          
        
        
      
      Is it compatible with fiber scheduler?
        
           Updated by Eregon (Benoit Daloze) over 2 years ago
          Updated by Eregon (Benoit Daloze) over 2 years ago
          
          
        
        
      
      ko1 (Koichi Sasada) wrote:
For (2), it is important to supervise thousands of ractors.
That currently (AFAIK) means thousands of OS threads, and that AFAIK results in very bad performance or scheduling from the kernel.
Is there a real use-case for so many Ractors?
        
           Updated by ko1 (Koichi Sasada) over 2 years ago
          Updated by ko1 (Koichi Sasada) over 2 years ago
          
          
        
        
      
      Eregon (Benoit Daloze) wrote in #note-3:
That currently (AFAIK) means thousands of OS threads, and that AFAIK results in very bad performance or scheduling from the kernel.
Is there a real use-case for so many Ractors?
This is why I'm working on MaNy project (M:N threads).
        
           Updated by ko1 (Koichi Sasada) over 2 years ago
          Updated by ko1 (Koichi Sasada) over 2 years ago
          
          
        
        
      
      - Description updated (diff)
        
           Updated by ioquatix (Samuel Williams) over 2 years ago
          Updated by ioquatix (Samuel Williams) over 2 years ago
          
          
        
        
      
      Is there a real use-case for so many Ractors?
This is why I'm working on MaNy project (M:N threads).
So it sounds like there is no use case?
At most, 2x ractors as the number of CPU cores should be enough to saturate a system, no?
(I'm not against the proposal, it seems pretty reasonable to me).
        
           Updated by ko1 (Koichi Sasada) over 2 years ago
          Updated by ko1 (Koichi Sasada) over 2 years ago
          
          
        
        
      
      - Description updated (diff)
        
           Updated by ko1 (Koichi Sasada) almost 2 years ago
          Updated by ko1 (Koichi Sasada) almost 2 years ago
          
          
        
        
      
      - Target version changed from 3.3 to 3.4
        
           Updated by hsbt (Hiroshi SHIBATA) over 1 year ago
          Updated by hsbt (Hiroshi SHIBATA) over 1 year ago
          
          
        
        
      
      - Status changed from Open to Assigned
        
           Updated by hsbt (Hiroshi SHIBATA) 10 months ago
          Updated by hsbt (Hiroshi SHIBATA) 10 months ago
          
          
        
        
      
      - Target version changed from 3.4 to 3.5