Skip to content

Multithreaded code with function calls fail in precompiled JRuby classes #6210

@mikefkim

Description

@mikefkim

On JRuby 9.2.9.0, we can successfully run code that calls functions within a Thread from pre-compiled JRuby classes.

The exact same situation (pre-compiled JRuby classes, multi-threaded code that calls a function) crashes on JRuby 9.2.10.0 and JRuby 9.2.11.0 (haven't tested 9.2.11.1 or 9.3).

We are running JDK1.8, and have been able to reproduce this on MacOS, Ubuntu, and Alpine. We have also been able to reproduce with both the default JRUBY_OPTS, and also with compile.invokedynamic=true.

FWIW, the same code with NUM_THREADS = 1 succeeds when run from a pre-compiled JRuby class (fails for NUM_THREADS > 1)

Here are the steps to reproduce the bug:

  1. Using JRuby 9.2.10.0+, compile the following code using jrubyc
# test.rb

NUM_THREADS = 16
SAMPLE_SIZE = 10_000

def sample
  a = Array.new(SAMPLE_SIZE) { |i| rand }
  a.inject(0.0, :+) / SAMPLE_SIZE.to_f
end

means = Array.new(NUM_THREADS) { 0 }

# Works, if not called within a Thread
# NUM_THREADS.times { |i| means[i] = sample }

threads = []
NUM_THREADS.times { |i|
  threads << Thread.new {
    # Crashes when `sample` is called with threads
    means[i] = sample

    # Works, if function body of `sample` is called in the thread directly
    # a = Array.new(SAMPLE_SIZE) { |i| rand }
    # means[i] = a.inject(0.0, :+) / SAMPLE_SIZE.to_f
  }
}
threads.map(&:join)

puts means.inject(0.0, :+) / NUM_THREADS.to_f
  1. Run the compiled class: jruby test.class

Expected Behavior

When compiled and run using JRuby 9.2.9.0, this test should return a number near 0.5.

Actual Behavior

When compiled and run using JRuby 9.2.10.0 and JRuby 9.2.11.0 (again, haven't tested beyond those), this same test crashes with the following stack trace:

warning: thread "Ruby-0-Thread-2: test.class:25" terminated with exception (report_on_exception is true):
warning: thread "Ruby-0-Thread-1: test.class:25" terminated with exception (report_on_exception is true):
java.lang.NullPointerException
	at org.jruby.ir.persistence.IRReaderStream.decodeEncoding(IRReaderStream.java:94)
	at org.jruby.ir.persistence.IRReaderStream.decodeSymbolFromConstantPool(IRReaderStream.java:126)
	at org.jruby.ir.persistence.IRReaderStream.decodeConstantPool(IRReaderStream.java:201)
	at org.jruby.ir.persistence.IRReaderStream.decodeInstructionsAt(IRReaderStream.java:208)
	at org.jruby.ir.persistence.IRReader.lambda$load$0(IRReader.java:47)
	at org.jruby.ir.interpreter.InterpreterContext.getEngine(InterpreterContext.java:74)
	at org.jruby.ir.interpreter.InterpreterContext.hasExplicitCallProtocol(InterpreterContext.java:206)
	at org.jruby.internal.runtime.methods.MixedModeIRMethod.INTERPRET_METHOD(MixedModeIRMethod.java:116)
	at org.jruby.internal.runtime.methods.MixedModeIRMethod.call(MixedModeIRMethod.java:108)
	at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:192)
	at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:141)
	at org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:345)
	at org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:72)
	at org.jruby.ir.interpreter.Interpreter.INTERPRET_BLOCK(Interpreter.java:116)
	at org.jruby.runtime.MixedModeIRBlockBody.commonYieldPath(MixedModeIRBlockBody.java:136)
	at org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:60)
	at org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:52)
	at org.jruby.runtime.Block.call(Block.java:139)
	at org.jruby.RubyProc.call(RubyProc.java:318)
	at org.jruby.internal.runtime.RubyRunnable.run(RubyRunnable.java:105)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NullPointerException
	at org.jruby.ir.persistence.IRReaderStream.decode(IRReaderStream.java:478)
	at org.jruby.ir.persistence.IRReaderStream.decodeOperand(IRReaderStream.java:371)
	at org.jruby.ir.persistence.IRReaderStream.decodeVariable(IRReaderStream.java:380)
	at org.jruby.ir.instructions.LoadImplicitClosureInstr.decode(LoadImplicitClosureInstr.java:33)
	at org.jruby.ir.persistence.IRReaderStream.decodeInstr(IRReaderStream.java:289)
	at org.jruby.ir.persistence.IRReaderStream.decodeInstructionsAt(IRReaderStream.java:218)
	at org.jruby.ir.persistence.IRReader.lambda$load$0(IRReader.java:47)
	at org.jruby.ir.interpreter.InterpreterContext.getEngine(InterpreterContext.java:74)
	at org.jruby.ir.interpreter.InterpreterContext.hasExplicitCallProtocol(InterpreterContext.java:206)
	at org.jruby.internal.runtime.methods.MixedModeIRMethod.INTERPRET_METHOD(MixedModeIRMethod.java:116)
	at org.jruby.internal.runtime.methods.MixedModeIRMethod.call(MixedModeIRMethod.java:108)
	at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:192)
	at org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:354)
	at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:143)
	at org.jruby.ir.interpreter.InterpreterEngine.processCall(InterpreterEngine.java:345)
	at org.jruby.ir.interpreter.StartupInterpreterEngine.interpret(StartupInterpreterEngine.java:72)
	at org.jruby.ir.interpreter.Interpreter.INTERPRET_BLOCK(Interpreter.java:116)
	at org.jruby.runtime.MixedModeIRBlockBody.commonYieldPath(MixedModeIRBlockBody.java:136)
	at org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:60)
	at org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:52)
	at org.jruby.runtime.Block.call(Block.java:139)
	at org.jruby.RubyProc.call(RubyProc.java:318)
	at org.jruby.internal.runtime.RubyRunnable.run(RubyRunnable.java:105)
	at java.lang.Thread.run(Thread.java:745)
Unhandled Java exception: java.lang.NullPointerException
java.lang.NullPointerException: null
                   decode at org/jruby/ir/persistence/IRReaderStream.java:478
            decodeOperand at org/jruby/ir/persistence/IRReaderStream.java:371
           decodeVariable at org/jruby/ir/persistence/IRReaderStream.java:380
                   decode at org/jruby/ir/instructions/LoadImplicitClosureInstr.java:33
              decodeInstr at org/jruby/ir/persistence/IRReaderStream.java:289
     decodeInstructionsAt at org/jruby/ir/persistence/IRReaderStream.java:218
            lambda$load$0 at org/jruby/ir/persistence/IRReader.java:47
                getEngine at org/jruby/ir/interpreter/InterpreterContext.java:74
  hasExplicitCallProtocol at org/jruby/ir/interpreter/InterpreterContext.java:206
         INTERPRET_METHOD at org/jruby/internal/runtime/methods/MixedModeIRMethod.java:116
                     call at org/jruby/internal/runtime/methods/MixedModeIRMethod.java:108
                     call at org/jruby/internal/runtime/methods/DynamicMethod.java:192
             cacheAndCall at org/jruby/runtime/callsite/CachingCallSite.java:354
                     call at org/jruby/runtime/callsite/CachingCallSite.java:143
              processCall at org/jruby/ir/interpreter/InterpreterEngine.java:345
                interpret at org/jruby/ir/interpreter/StartupInterpreterEngine.java:72
          INTERPRET_BLOCK at org/jruby/ir/interpreter/Interpreter.java:116
          commonYieldPath at org/jruby/runtime/MixedModeIRBlockBody.java:136
                     call at org/jruby/runtime/IRBlockBody.java:60
                     call at org/jruby/runtime/IRBlockBody.java:52
                     call at org/jruby/runtime/Block.java:139
                     call at org/jruby/RubyProc.java:318
                      run at org/jruby/internal/runtime/RubyRunnable.java:105
                      run at java/lang/Thread.java:745

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