summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2020-09-19 23:34:02 (GMT)
committerSamuel Williams <samuel.williams@oriontransfer.co.nz>2020-09-20 21:51:33 (GMT)
commit501fff14c7657f769d68f90de98fd2ebccb807fb (patch)
tree10dfcaf36b27dcd6b83268f9b0de2516fed41ec2
parentb6d599d76ec85422bea16b63f105985cf08e04bd (diff)
When setting current thread scheduler to nil, invoke `#close`.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/3557
-rw-r--r--common.mk1
-rw-r--r--eval.c11
-rw-r--r--internal/scheduler.h2
-rw-r--r--scheduler.c17
-rw-r--r--test/fiber/scheduler.rb15
-rw-r--r--test/fiber/test_scheduler.rb25
-rw-r--r--thread.c10
7 files changed, 75 insertions, 6 deletions
diff --git a/common.mk b/common.mk
index f5ea771..936846d 100644
--- a/common.mk
+++ b/common.mk
@@ -5214,6 +5214,7 @@ eval.$(OBJEXT): $(top_srcdir)/internal/object.h
eval.$(OBJEXT): $(top_srcdir)/internal/serial.h
eval.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
eval.$(OBJEXT): $(top_srcdir)/internal/string.h
+eval.$(OBJEXT): $(top_srcdir)/internal/thread.h
eval.$(OBJEXT): $(top_srcdir)/internal/variable.h
eval.$(OBJEXT): $(top_srcdir)/internal/vm.h
eval.$(OBJEXT): $(top_srcdir)/internal/warnings.h
diff --git a/eval.c b/eval.c
index 0b51b83..43a5084 100644
--- a/eval.c
+++ b/eval.c
@@ -28,6 +28,7 @@
#include "internal/io.h"
#include "internal/mjit.h"
#include "internal/object.h"
+#include "internal/thread.h"
#include "internal/variable.h"
#include "iseq.h"
#include "mjit.h"
@@ -158,6 +159,13 @@ rb_ec_teardown(rb_execution_context_t *ec)
}
static void
+rb_ec_scheduler_finalize(rb_execution_context_t *ec)
+{
+ rb_thread_t *thread = rb_ec_thread_ptr(ec);
+ rb_thread_scheduler_set(thread->self, Qnil);
+}
+
+static void
rb_ec_finalize(rb_execution_context_t *ec)
{
ruby_sig_finalize();
@@ -270,6 +278,9 @@ rb_ec_cleanup(rb_execution_context_t *ec, volatile int ex)
}
}
+ // If the user code defined a scheduler for the top level thread, run it:
+ rb_ec_scheduler_finalize(ec);
+
mjit_finish(true); // We still need ISeqs here.
rb_ec_finalize(ec);
diff --git a/internal/scheduler.h b/internal/scheduler.h
index 54f59f1..73915ad 100644
--- a/internal/scheduler.h
+++ b/internal/scheduler.h
@@ -14,6 +14,8 @@
VALUE rb_scheduler_timeout(struct timeval *timeout);
+VALUE rb_scheduler_close(VALUE scheduler);
+
VALUE rb_scheduler_kernel_sleep(VALUE scheduler, VALUE duration);
VALUE rb_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv);
diff --git a/scheduler.c b/scheduler.c
index f038eed..2dfecaf 100644
--- a/scheduler.c
+++ b/scheduler.c
@@ -11,9 +11,13 @@
#include "internal/scheduler.h"
#include "ruby/io.h"
-static ID id_kernel_sleep;
+static ID id_close;
+
static ID id_block;
static ID id_unblock;
+
+static ID id_kernel_sleep;
+
static ID id_io_read;
static ID id_io_write;
static ID id_io_wait;
@@ -21,14 +25,23 @@ static ID id_io_wait;
void
Init_Scheduler(void)
{
- id_kernel_sleep = rb_intern_const("kernel_sleep");
+ id_close = rb_intern_const("close");
+
id_block = rb_intern_const("block");
id_unblock = rb_intern_const("unblock");
+
+ id_kernel_sleep = rb_intern_const("kernel_sleep");
+
id_io_read = rb_intern_const("io_read");
id_io_write = rb_intern_const("io_write");
id_io_wait = rb_intern_const("io_wait");
}
+VALUE rb_scheduler_close(VALUE scheduler)
+{
+ return rb_funcall(scheduler, id_close, 0);
+}
+
VALUE
rb_scheduler_timeout(struct timeval *timeout) {
if (timeout) {
diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb
index 43edcb2..10854aa 100644
--- a/test/fiber/scheduler.rb
+++ b/test/fiber/scheduler.rb
@@ -19,6 +19,8 @@ class Scheduler
@writable = {}
@waiting = {}
+ @closed = false
+
@lock = Mutex.new
@locking = 0
@ready = []
@@ -96,6 +98,19 @@ class Scheduler
@urgent = nil
end
+ def close
+ self.run
+ ensure
+ @closed = true
+
+ # We freeze to detect any inadvertant modifications after the scheduler is closed:
+ self.freeze
+ end
+
+ def closed?
+ @closed
+ end
+
def current_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb
index 7acf63d..23fd8b4 100644
--- a/test/fiber/test_scheduler.rb
+++ b/test/fiber/test_scheduler.rb
@@ -10,4 +10,29 @@ class TestFiberScheduler < Test::Unit::TestCase
end
end
end
+
+ def test_closed_at_thread_exit
+ scheduler = Scheduler.new
+
+ thread = Thread.new do
+ Thread.current.scheduler = scheduler
+ end
+
+ thread.join
+
+ assert scheduler.closed?
+ end
+
+ def test_closed_when_set_to_nil
+ scheduler = Scheduler.new
+
+ thread = Thread.new do
+ Thread.current.scheduler = scheduler
+ Thread.current.scheduler = nil
+
+ assert scheduler.closed?
+ end
+
+ thread.join
+ end
end
diff --git a/thread.c b/thread.c
index b3b7a69..53bfbe8 100644
--- a/thread.c
+++ b/thread.c
@@ -748,10 +748,7 @@ thread_do_start(rb_thread_t *th)
rb_bug("unreachable");
}
- VALUE scheduler = th->scheduler;
- if (scheduler != Qnil) {
- rb_funcall(scheduler, rb_intern("run"), 0);
- }
+ rb_thread_scheduler_set(th->self, Qnil);
}
void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec);
@@ -3732,6 +3729,11 @@ rb_thread_scheduler_set(VALUE thread, VALUE scheduler)
VM_ASSERT(th);
+ // We invoke Scheduler#close when setting it to something else, to ensure the previous scheduler runs to completion before changing the scheduler. That way, we do not need to consider interactions, e.g., of a Fiber from the previous scheduler with the new scheduler.
+ if (th->scheduler != Qnil) {
+ rb_scheduler_close(th->scheduler);
+ }
+
th->scheduler = scheduler;
return th->scheduler;