Actions
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...
Actions
Like0
Like0Like0Like0Like1Like0Like0Like0Like0Like0