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
signaller.join
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

View File

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