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)
: addsr
to the waiting set. -
Ractor::Selector#remove(r)
: removesr
from 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)
ifrs
is 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: true
keyword 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
r
is closing, then#wait
removesr
automatically. - If there is no waiting ractors, it raises an exception (now
Ractor::Error
is 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
Selector
is 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 ioquatix (Samuel Williams) almost 2 years ago
Is it compatible with fiber scheduler?
Updated by Eregon (Benoit Daloze) almost 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) almost 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 ioquatix (Samuel Williams) over 1 year 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) 12 months ago
- Target version changed from 3.3 to 3.4
Updated by hsbt (Hiroshi SHIBATA) 8 months ago
- Status changed from Open to Assigned