thread_sync.c (rb_mutex_synchronize): only unlock if we own the mutex

If an exception is raised inside Mutex#sleep (via ConditionVariable#wait),
we cannot guarantee we can own the mutex in the ensure callback.

However, who owns the mutex at that point does not matter.  What
matters is the Mutex is usable after an exception occurs.

* thread_sync.c (rb_mutex_synchronize): only unlock if we own the mutex

* spec/ruby/library/conditionvariable/wait_spec.rb: only test lock
  usability after thread kill.  Who owns the lock at any
  particular moment is an implementation detail which we cannot
  easily guarantee.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64441 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2018-08-18 06:33:49 +00:00
parent 6e0d69e4a7
commit 647fc1227a
2 changed files with 56 additions and 44 deletions

View File

@ -23,21 +23,15 @@ describe "ConditionVariable#wait" do
th.join th.join
end end
it "reacquires the lock even if the thread is killed" do it "the lock remains usable even if the thread is killed" do
m = Mutex.new m = Mutex.new
cv = ConditionVariable.new cv = ConditionVariable.new
in_synchronize = false in_synchronize = false
owned = nil
th = Thread.new do th = Thread.new do
m.synchronize do m.synchronize do
in_synchronize = true in_synchronize = true
begin
cv.wait(m) cv.wait(m)
ensure
owned = m.owned?
$stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
end
end end
end end
@ -49,24 +43,19 @@ describe "ConditionVariable#wait" do
th.kill th.kill
th.join th.join
owned.should == true m.try_lock.should == true
m.unlock
end end
it "reacquires the lock even if the thread is killed after being signaled" do it "lock remains usable even if the thread is killed after being signaled" do
m = Mutex.new m = Mutex.new
cv = ConditionVariable.new cv = ConditionVariable.new
in_synchronize = false in_synchronize = false
owned = nil
th = Thread.new do th = Thread.new do
m.synchronize do m.synchronize do
in_synchronize = true in_synchronize = true
begin
cv.wait(m) cv.wait(m)
ensure
owned = m.owned?
$stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
end
end end
end end
@ -84,7 +73,8 @@ describe "ConditionVariable#wait" do
} }
th.join th.join
owned.should == true m.try_lock.should == true
m.unlock
end end
it "supports multiple Threads waiting on the same ConditionVariable and Mutex" do it "supports multiple Threads waiting on the same ConditionVariable and Mutex" do

View File

@ -321,21 +321,14 @@ rb_mutex_owned_p(VALUE self)
return owned; return owned;
} }
static const char * static void
rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th) mutex_do_unlock(rb_mutex_t *mutex, rb_thread_t *th)
{ {
const char *err = NULL;
if (mutex->th == 0) {
err = "Attempt to unlock a mutex which is not locked";
}
else if (mutex->th != th) {
err = "Attempt to unlock a mutex which is locked by another thread";
}
else {
struct sync_waiter *cur = 0, *next = 0; struct sync_waiter *cur = 0, *next = 0;
rb_mutex_t **th_mutex = &th->keeping_mutexes; rb_mutex_t **th_mutex = &th->keeping_mutexes;
VM_ASSERT(mutex->th == th);
mutex->th = 0; mutex->th = 0;
list_for_each_safe(&mutex->waitq, cur, next, node) { list_for_each_safe(&mutex->waitq, cur, next, node) {
list_del_init(&cur->node); list_del_init(&cur->node);
@ -358,6 +351,21 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th)
} }
*th_mutex = mutex->next_mutex; *th_mutex = mutex->next_mutex;
mutex->next_mutex = NULL; mutex->next_mutex = NULL;
}
static const char *
rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th)
{
const char *err = NULL;
if (mutex->th == 0) {
err = "Attempt to unlock a mutex which is not locked";
}
else if (mutex->th != th) {
err = "Attempt to unlock a mutex which is locked by another thread";
}
else {
mutex_do_unlock(mutex, th);
} }
return err; return err;
@ -497,6 +505,20 @@ mutex_sleep(int argc, VALUE *argv, VALUE self)
return rb_mutex_sleep(self, timeout); return rb_mutex_sleep(self, timeout);
} }
static VALUE
mutex_unlock_if_owned(VALUE self)
{
rb_thread_t *th = GET_THREAD();
rb_mutex_t *mutex;
GetMutexPtr(self, mutex);
/* we may not own the mutex if an exception occured */
if (mutex->th == th) {
mutex_do_unlock(mutex, th);
}
return Qfalse;
}
/* /*
* call-seq: * call-seq:
* mutex.synchronize { ... } -> result of the block * mutex.synchronize { ... } -> result of the block
@ -509,7 +531,7 @@ VALUE
rb_mutex_synchronize(VALUE mutex, VALUE (*func)(VALUE arg), VALUE arg) rb_mutex_synchronize(VALUE mutex, VALUE (*func)(VALUE arg), VALUE arg)
{ {
rb_mutex_lock(mutex); rb_mutex_lock(mutex);
return rb_ensure(func, arg, rb_mutex_unlock, mutex); return rb_ensure(func, arg, mutex_unlock_if_owned, mutex);
} }
/* /*