Ensure fiber scheduler re-acquires mutex when interrupted from sleep. (#12158)

[Bug #20907]
This commit is contained in:
Samuel Williams 2024-11-24 12:54:12 +13:00 committed by GitHub
parent 31997661e4
commit a8c2d5e7be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2024-11-23 23:54:31 +00:00
Merged-By: ioquatix <samuel@codeotaku.com>
2 changed files with 62 additions and 28 deletions

View File

@ -182,4 +182,32 @@ class TestFiberScheduler < Test::Unit::TestCase
thread.join thread.join
signaller.join signaller.join
end end
def test_condition_variable
condition_variable = ::Thread::ConditionVariable.new
mutex = ::Thread::Mutex.new
error = nil
thread = Thread.new do
Thread.current.report_on_exception = false
scheduler = Scheduler.new
Fiber.set_scheduler scheduler
fiber = Fiber.schedule do
begin
mutex.synchronize do
condition_variable.wait(mutex)
end
rescue => error
end
end
fiber.raise(RuntimeError)
end
thread.join
assert_kind_of RuntimeError, error
end
end end

View File

@ -548,48 +548,54 @@ rb_mutex_abandon_all(rb_mutex_t *mutexes)
} }
#endif #endif
static VALUE struct rb_mutex_sleep_arguments {
rb_mutex_sleep_forever(VALUE self) VALUE self;
{ VALUE timeout;
rb_thread_sleep_deadly_allow_spurious_wakeup(self, Qnil, 0); };
return Qnil;
}
static VALUE static VALUE
rb_mutex_wait_for(VALUE time) mutex_sleep_begin(VALUE _arguments)
{ {
rb_hrtime_t *rel = (rb_hrtime_t *)time; struct rb_mutex_sleep_arguments *arguments = (struct rb_mutex_sleep_arguments *)_arguments;
/* permit spurious check */ VALUE timeout = arguments->timeout;
return RBOOL(sleep_hrtime(GET_THREAD(), *rel, 0)); VALUE woken = Qtrue;
VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
rb_fiber_scheduler_kernel_sleep(scheduler, timeout);
}
else {
if (NIL_P(timeout)) {
rb_thread_sleep_deadly_allow_spurious_wakeup(arguments->self, Qnil, 0);
}
else {
struct timeval timeout_value = rb_time_interval(timeout);
rb_hrtime_t relative_timeout = rb_timeval2hrtime(&timeout_value);
/* permit spurious check */
woken = RBOOL(sleep_hrtime(GET_THREAD(), relative_timeout, 0));
}
}
return woken;
} }
VALUE VALUE
rb_mutex_sleep(VALUE self, VALUE timeout) rb_mutex_sleep(VALUE self, VALUE timeout)
{ {
struct timeval t;
VALUE woken = Qtrue;
if (!NIL_P(timeout)) { if (!NIL_P(timeout)) {
t = rb_time_interval(timeout); // Validate the argument:
rb_time_interval(timeout);
} }
rb_mutex_unlock(self); rb_mutex_unlock(self);
time_t beg = time(0); time_t beg = time(0);
VALUE scheduler = rb_fiber_scheduler_current(); struct rb_mutex_sleep_arguments arguments = {
if (scheduler != Qnil) { .self = self,
rb_fiber_scheduler_kernel_sleep(scheduler, timeout); .timeout = timeout,
mutex_lock_uninterruptible(self); };
}
else { VALUE woken = rb_ensure(mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self);
if (NIL_P(timeout)) {
rb_ensure(rb_mutex_sleep_forever, self, mutex_lock_uninterruptible, self);
}
else {
rb_hrtime_t rel = rb_timeval2hrtime(&t);
woken = rb_ensure(rb_mutex_wait_for, (VALUE)&rel, mutex_lock_uninterruptible, self);
}
}
RUBY_VM_CHECK_INTS_BLOCKING(GET_EC()); RUBY_VM_CHECK_INTS_BLOCKING(GET_EC());
if (!woken) return Qnil; if (!woken) return Qnil;