summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bootstraptest/test_ractor.rb96
-rw-r--r--ractor.c15
-rw-r--r--ractor.rb12
3 files changed, 122 insertions, 1 deletions
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index 3d0fdfa283..5f7cba3947 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -1617,4 +1617,100 @@ assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{
eval("Ractor.new{}.take", nil, "test_ractor.rb", 1)
}
+## Ractor::Selector
+
+# Selector#empty? returns true
+assert_equal 'true', %q{
+ s = Ractor::Selector.new
+ s.empty?
+}
+
+# Selector#empty? returns false if there is target ractors
+assert_equal 'false', %q{
+ s = Ractor::Selector.new
+ s.add Ractor.new{}
+ s.empty?
+}
+
+# Selector#clear removes all ractors from the waiting list
+assert_equal 'true', %q{
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ s.clear
+ s.empty?
+}
+
+# Selector#wait can wait multiple ractors
+assert_equal '[10, 20, true]', %q{
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ r, v = s.wait
+ vs = []
+ vs << v
+ r, v = s.wait
+ vs << v
+ [*vs.sort, s.empty?]
+}
+
+# Selector#wait can wait multiple ractors with receiving.
+assert_equal '30', %q{
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+
+ results = []
+ until s.empty?
+ results << s.wait
+
+ # Note that s.wait can raise an exception because other Ractors/Threads
+ # can take from the same ractors in the waiting set.
+ # In this case there is no other takers so `s.wait` doesn't raise an error.
+ end
+
+ results.size
+}
+
+# Selector#wait can support dynamic addition
+assert_equal '600', %q{
+ RN = 100
+ s = Ractor::Selector.new
+ rs = RN.times.map{
+ Ractor.new{
+ Ractor.main << Ractor.new{ Ractor.yield :v3; :v4 }
+ Ractor.main << Ractor.new{ Ractor.yield :v5; :v6 }
+ Ractor.yield :v1
+ :v2
+ }
+ }
+
+ rs.each{|r| s.add(r)}
+ h = {v1: 0, v2: 0, v3: 0, v4: 0, v5: 0, v6: 0}
+
+ loop do
+ case s.wait receive: true
+ in :receive, r
+ s.add r
+ in r, v
+ h[v] += 1
+ break if h.all?{|k, v| v == RN}
+ end
+ end
+
+ h.sum{|k, v| v}
+}
+
+# Selector should be GCed (free'ed) withtou trouble
+assert_equal 'ok', %q{
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+ :ok
+}
+
end # if !ENV['GITHUB_WORKFLOW']
diff --git a/ractor.c b/ractor.c
index 1c5abbc422..b586432fb6 100644
--- a/ractor.c
+++ b/ractor.c
@@ -979,7 +979,7 @@ ractor_take_will_lock(rb_ractor_t *r, struct rb_ractor_basket *b)
RACTOR_LOCK(r);
{
- taken =ractor_take_will(r, b);
+ taken = ractor_take_will(r, b);
}
RACTOR_UNLOCK(r);
@@ -1009,6 +1009,8 @@ ractor_register_take(rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *t
}
else if (!is_take && ractor_take_has_will(r)) {
RUBY_DEBUG_LOG("has_will");
+ VM_ASSERT(config != NULL);
+ config->closed = true;
}
else if (r->sync.outgoing_port_closed) {
closed = true;
@@ -1515,6 +1517,13 @@ ractor_selector_clear(rb_execution_context_t *ec, VALUE selv)
return selv;
}
+static VALUE
+ractor_selector_empty_p(rb_execution_context_t *ec, VALUE selv)
+{
+ struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
+ return s->take_ractors->num_entries == 0 ? Qtrue : Qfalse;
+}
+
static int
ractor_selector_wait_i(st_data_t key, st_data_t val, st_data_t dat)
{
@@ -1687,6 +1696,10 @@ ractor_selector_wait(rb_execution_context_t *ec, VALUE selv, VALUE do_receivev,
}
break;
}
+ case basket_type_will:
+ // no more messages
+ ractor_selector_remove(ec, selv, taken_basket.sender);
+ break;
default:
break;
}
diff --git a/ractor.rb b/ractor.rb
index 6c57d65f87..5620eeec38 100644
--- a/ractor.rb
+++ b/ractor.rb
@@ -429,6 +429,18 @@ class Ractor
end
# call-seq:
+ # selector.empty?
+ #
+ # Returns true if the number of ractors in the waiting set at the current time is zero.
+ #
+ # Note that even if <tt>#empty?</tt> returns false, the subsequent <tt>#wait</tt>
+ # may raise an exception because other ractors may close the target ractors.
+ #
+ def empty?
+ __builtin_ractor_selector_empty_p
+ end
+
+ # call-seq:
# selector.wait(receive: false, yield_value: yield_value, move: false) -> [ractor or symbol, value]
#
# Waits Ractor events. It is lighter than Ractor.select() for many ractors.