Skip to content

Mutex sleeps indefinitely with timeout <= 0.0001 seconds #4862

@camlow325

Description

@camlow325

Environment

Provide at least:

  • JRuby version (jruby -v) and command line (flags, JRUBY_OPTS, etc)

JRuby 9.1.14.0

  • Operating system and platform (e.g. uname -a)

Windows

Expected Behavior

  • Describe your expectation of how JRuby should behave, perhaps by showing how CRuby/MRI behaves.

When sleep() is called with a timeout which is 0.0001 or less (including 0) seconds on a locked mutex, the sleep() appears to be indefinite. For MRI 2.4.2, however, the sleep times out quickly with these smaller values.

  • Provide an executable Ruby script or a link to an example repository.

I ran the following script to reproduce this issue:

m = Mutex.new
m.synchronize do
  [0.001, 0.0001, 0].each do |timeout|
    puts "About to sleep for #{timeout} seconds..."
    m.sleep(timeout)
    puts "Done sleeping for #{timeout} seconds"
  end
end

With MRI 2.4.2, the following output appears and the script ends normally:

About to sleep for 0.001 seconds...
Done sleeping for 0.001 seconds
About to sleep for 0.0001 seconds...
Done sleeping for 0.0001 seconds
About to sleep for 0 seconds...
Done sleeping for 0 seconds

Actual Behavior

  • Describe or show the actual behavior.

With JRuby 9.1.14.0, only part of the output appears:

About to sleep for 0.001 seconds...
Done sleeping for 0.001 seconds
About to sleep for 0.0001 seconds...

Even after leaving the script running for several minutes, the next expected message never appears: "Done sleeping for 0.0001 seconds". The same apparent hang appears if a sleep for 0 seconds - without the preceding sleep for 0.0001 seconds - occurs.

Here is a backtrace for the main thread while in the hang state:

"main" #1 prio=5 os_prio=0 tid=0x000000000355e000 nid=0x7ff0 waiting on condition [0x000000000375d000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000e0efc748> (a java.util.concurrent.Semaphore$NonfairSync)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos(AbstractQueuedSynchronizer.java:1037)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos(AbstractQueuedSynchronizer.java:1328)
        at java.util.concurrent.Semaphore.tryAcquire(Semaphore.java:409)
        at org.jruby.RubyThread$SleepTask2.run(RubyThread.java:1445)
        at org.jruby.RubyThread$SleepTask2.run(RubyThread.java:1434)
        at org.jruby.RubyThread.executeTask(RubyThread.java:1486)
        at org.jruby.RubyThread.sleep(RubyThread.java:1354)
        at org.jruby.ext.thread.Mutex.sleep(Mutex.java:135)
        at org.jruby.ext.thread.Mutex$INVOKER$i$sleep.call(Mutex$INVOKER$i$sleep.gen)
        at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:153)
        at C_3a_.mutex_sleep.invokeOther4:sleep(mutex_sleep.rb:5)
        at C_3a_.mutex_sleep.RUBY$block$\=mutex_sleep\,rb$1(mutex_sleep.rb:5)
        at java.lang.invoke.LambdaForm$DMH/468121027.invokeStatic_L8_L(LambdaForm$DMH)
        at java.lang.invoke.LambdaForm$MH/422392391.invokeExact_MT(LambdaForm$MH)
        at org.jruby.runtime.CompiledIRBlockBody.yieldDirect(CompiledIRBlockBody.java:156)
        at org.jruby.runtime.BlockBody.yield(BlockBody.java:114)
        at org.jruby.runtime.Block.yield(Block.java:165)
        at org.jruby.RubyArray.each(RubyArray.java:1734)
        at org.jruby.RubyArray$INVOKER$i$0$0$each.call(RubyArray$INVOKER$i$0$0$each.gen)
        at org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:308)
        at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:137)
        at org.jruby.runtime.callsite.CachingCallSite.callIter(CachingCallSite.java:142)
        at C_3a_.mutex_sleep.invokeOther12:each(mutex_sleep.rb:3)
        at C_3a_.mutex_sleep.RUBY$block$\=mutex_sleep\,rb$0(mutex_sleep.rb:3)
        at java.lang.invoke.LambdaForm$DMH/468121027.invokeStatic_L8_L(LambdaForm$DMH)
        at java.lang.invoke.LambdaForm$MH/422392391.invokeExact_MT(LambdaForm$MH)
        at org.jruby.runtime.CompiledIRBlockBody.yieldDirect(CompiledIRBlockBody.java:156)
        at org.jruby.runtime.IRBlockBody.yieldSpecific(IRBlockBody.java:80)
        at org.jruby.runtime.Block.yieldSpecific(Block.java:134)
        at org.jruby.ext.thread.Mutex.synchronize(Mutex.java:148)
        at org.jruby.ext.thread.Mutex$INVOKER$i$0$0$synchronize.call(Mutex$INVOKER$i$0$0$synchronize.gen)
        at org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:308)
        at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:137)
        at org.jruby.runtime.callsite.CachingCallSite.callIter(CachingCallSite.java:142)
        at C_3a_.mutex_sleep.invokeOther15:synchronize(mutex_sleep.rb:2)
        at C_3a_.mutex_sleep.RUBY$script(mutex_sleep.rb:2)
        at java.lang.invoke.LambdaForm$DMH/1450495309.invokeStatic_L7_L(LambdaForm$DMH)
        at java.lang.invoke.LambdaForm$BMH/84739718.reinvoke(LambdaForm$BMH)
        at java.lang.invoke.LambdaForm$MH/632249781.invoker(LambdaForm$MH)
        at java.lang.invoke.LambdaForm$MH/434176574.invokeExact_MT(LambdaForm$MH)
        at java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:627)
        at org.jruby.ir.Compiler$1.load(Compiler.java:95)
        at org.jruby.Ruby.runScript(Ruby.java:828)
        at org.jruby.Ruby.runNormally(Ruby.java:747)
        at org.jruby.Ruby.runNormally(Ruby.java:765)
        at org.jruby.Ruby.runFromMain(Ruby.java:578)
        at org.jruby.Main.doRunFromMain(Main.java:417)
        at org.jruby.Main.internalRun(Main.java:305)
        at org.jruby.Main.run(Main.java:232)
        at org.jruby.Main.main(Main.java:204

I had some code which would sleep for 0 seconds periodically to allow a waiter to take the mutex and change some state to interrupt a long running process. Bumping the timeout up to a slightly higher value - like 0.001 seconds - is a reasonable workaround. Just thought it might be good to point out the discrepancy as compared to MRI Ruby behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions