From 708d2170c9b403c9c5c1af609f3a3b569b81cd29 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 27 Apr 2021 19:21:47 +0200 Subject: [PATCH] Use Mutex#owned? to correctly check if the Mutex is owned by the current Thread or Fiber * In Ruby >= 3, Mutex are held per Fiber, not per Thread. * Fixes https://github.com/rspec/rspec-support/issues/501 --- lib/rspec/support/reentrant_mutex.rb | 11 +++++----- spec/rspec/support/reentrant_mutex_spec.rb | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/rspec/support/reentrant_mutex.rb b/lib/rspec/support/reentrant_mutex.rb index e61b953f3..6d24b119b 100644 --- a/lib/rspec/support/reentrant_mutex.rb +++ b/lib/rspec/support/reentrant_mutex.rb @@ -17,7 +17,6 @@ class << self # @private class ReentrantMutex def initialize - @owner = nil @count = 0 @mutex = Mutex.new end @@ -32,16 +31,16 @@ def synchronize private def enter - @mutex.lock if @owner != Thread.current - @owner = Thread.current + @mutex.lock unless @mutex.owned? @count += 1 end def exit + unless @mutex.owned? + raise ThreadError, "Attempt to unlock a mutex which is locked by another thread/fiber" + end @count -= 1 - return unless @count == 0 - @owner = nil - @mutex.unlock + @mutex.unlock if @count == 0 end end end diff --git a/spec/rspec/support/reentrant_mutex_spec.rb b/spec/rspec/support/reentrant_mutex_spec.rb index 036879811..1412d2102 100644 --- a/spec/rspec/support/reentrant_mutex_spec.rb +++ b/spec/rspec/support/reentrant_mutex_spec.rb @@ -34,4 +34,28 @@ mutex.synchronize { order.pass_to :thread, :resume_on => :sleep } order.join_all end + + if RUBY_VERSION >= '3.0' + it 'waits when trying to lock from another Fiber' do + mutex.synchronize do + ready = false + f = Fiber.new do + expect { + ready = true + mutex.send(:enter) + raise 'should reach here: mutex is already locked on different Fiber' + }.to raise_error(Exception, 'waited correctly') + end + + main_thread = Thread.current + + t = Thread.new do + Thread.pass until ready and main_thread.stop? + main_thread.raise Exception, 'waited correctly' + end + f.resume + t.join + end + end + end end