Project

General

Profile

Actions

Bug #17827

closed

Monitor is not fiber safe

Added by ioquatix (Samuel Williams) 7 months ago. Updated 6 months ago.

Status:
Closed
Priority:
Normal
Target version:
-
[ruby-core:103591]

Description

According to our discussion here https://github.com/rspec/rspec-support/issues/501 it seems like typical implementation of per-thread reentrant mutex is no longer valid and can lead to some deadlock situation.

#!/usr/bin/env ruby

require 'monitor'

def monitor_failure
    m = Monitor.new

    f1 = Fiber.new do
        m.synchronize do
            puts "f1 A"
            Fiber.yield
            puts "f1 B"
        end
    end

    f2 = Fiber.new do
        m.synchronize do
            puts "f2 A"
            # Fiber.yield
            f1.resume
            puts "f2 B"
        end
    end

    f1.resume
    f2.resume
end

monitor_failure

Updated by Eregon (Benoit Daloze) 7 months ago

As discussed on that linked issue (https://github.com/rspec/rspec-support/issues/501#issuecomment-826315195),
the behavior should be the same as Mutex in this case, so it is a bug of the monitor stdlib.

Actions #2

Updated by Eregon (Benoit Daloze) 7 months ago

  • Description updated (diff)
Actions #3

Updated by Eregon (Benoit Daloze) 7 months ago

  • Backport changed from 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN to 2.6: DONTNEED, 2.7: DONTNEED, 3.0: REQUIRED

Updated by Eregon (Benoit Daloze) 7 months ago

  • Assignee set to Eregon (Benoit Daloze)
Actions #5

Updated by Eregon (Benoit Daloze) 7 months ago

  • Status changed from Open to Closed

Applied in changeset git|3a3b19b2bba49e5d6f1cf13764eb6dd701397be9.


Fix Monitor to lock per Fiber, like Mutex [Bug #17827]

Updated by Eregon (Benoit Daloze) 7 months ago

Fixed in https://github.com/ruby/ruby/commit/3a3b19b2bba49e5d6f1cf13764eb6dd701397be9.
This should be backported to 3.0 (I marked it as such).

I noticed something else while writing a test for this:

On 2.7:

ruby -e 'm=Mutex.new; m.lock; p 1; Fiber.new { m.lock }.resume'
1
Traceback (most recent call last):
    1: from -e:1:in `block in <main>'
-e:1:in `lock': deadlock; recursive locking (ThreadError)

On 3.0:

$ ruby -e 'm=Mutex.new; m.lock; p 1; Fiber.new { m.lock }.resume'           
1
-e:1:in `lock': No live threads left. Deadlock? (fatal)
1 threads, 1 sleeps current:0x000000000214f080 main thread:0x000000000214f080
* #<Thread:0x00000000021bb488 sleep_forever>
   rb_thread_t:0x000000000214f080 native:0x00007f05edc32b80 int:0   
    from -e:1:in `block in <main>'

$ ruby -e 'Thread.new {sleep}; sleep 0.1; m=Mutex.new; m.lock; p 1; Fiber.new { m.lock }.resume'
1
=> hangs

Same behavior on master for Monitor.new with my fix (ruby -rmonitor -e 'm=Monitor.new; m.enter; p 1; Fiber.new { p m.enter }.resume' => fatal, or hang if there is another Thread).

It would be nice to raise a ThreadError when detecting in Mutex#lock that the Mutex is owned by the same Thread but a different Fiber, if there is no Fiber scheduler.
That's more of a nice-to-have though.

Updated by ioquatix (Samuel Williams) 7 months ago

Thanks for your work. nagachika (Tomoyuki Chikanaga) can you please backport this? 🙏

Updated by nagachika (Tomoyuki Chikanaga) 6 months ago

  • Backport changed from 2.6: DONTNEED, 2.7: DONTNEED, 3.0: REQUIRED to 2.6: DONTNEED, 2.7: DONTNEED, 3.0: DONE

ruby_3_0 f9196de1dee6f5ab8b6fe115070b92775a3500fe merged revision(s) 3a3b19b2bba49e5d6f1cf13764eb6dd701397be9.

Updated by ioquatix (Samuel Williams) 6 months ago

nagachika (Tomoyuki Chikanaga) thank you so much for your effort.

Actions

Also available in: Atom PDF