summaryrefslogtreecommitdiff
path: root/scheduler.c
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2021-06-14 17:56:53 +1200
committernagachika <nagachika@ruby-lang.org>2021-09-26 20:49:54 +0900
commit5e9ec351044fb74f07f2a45a0dab1e226159b7e6 (patch)
treed88a0da7c222748f83792654eec867debd6624e7 /scheduler.c
parent98ac62de5cb03efec0fb32684c61c0d4df692e5a (diff)
Wake up join list within thread EC context. (#4471)
* Wake up join list within thread EC context. * Consume items from join list so that they are not re-executed. If `rb_fiber_scheduler_unblock` raises an exception, it can result in a segfault if `rb_threadptr_join_list_wakeup` is not within a valid EC. This change moves `rb_threadptr_join_list_wakeup` into the thread's top level EC which initially caused an infinite loop because on exception will retry. We explicitly remove items from the thread's join list to avoid this situation. * Verify the required scheduler interface. * Test several scheduler hooks methods with broken `unblock` implementation.
Diffstat (limited to 'scheduler.c')
-rw-r--r--scheduler.c24
1 files changed, 24 insertions, 0 deletions
diff --git a/scheduler.c b/scheduler.c
index 88db433f1e..66cbfc6f10 100644
--- a/scheduler.c
+++ b/scheduler.c
@@ -49,12 +49,36 @@ rb_scheduler_get(void)
return thread->scheduler;
}
+static void
+verify_interface(VALUE scheduler)
+{
+ if (!rb_respond_to(scheduler, id_block)) {
+ rb_raise(rb_eArgError, "Scheduler must implement #block!");
+ }
+
+ if (!rb_respond_to(scheduler, id_unblock)) {
+ rb_raise(rb_eArgError, "Scheduler must implement #unblock!");
+ }
+
+ if (!rb_respond_to(scheduler, id_kernel_sleep)) {
+ rb_raise(rb_eArgError, "Scheduler must implement #kernel_sleep!");
+ }
+
+ if (!rb_respond_to(scheduler, id_io_wait)) {
+ rb_raise(rb_eArgError, "Scheduler must implement #io_wait!");
+ }
+}
+
VALUE
rb_scheduler_set(VALUE scheduler)
{
rb_thread_t *thread = GET_THREAD();
VM_ASSERT(thread);
+ if (scheduler != Qnil) {
+ verify_interface(scheduler);
+ }
+
// 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 (thread->scheduler != Qnil) {
rb_scheduler_close(thread->scheduler);