JuliaInterop / JavaCall.jl

Call Java from Julia
http://juliainterop.github.io/JavaCall.jl
Other
118 stars 53 forks source link

Compatibility with Threads? #131

Open DrChainsaw opened 3 years ago

DrChainsaw commented 3 years ago

Trying to run jcall in a separate thread kills the julia session:

PS > julia -i -t 8 --project -e "using JavaCall; JavaCall.init()"
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.1 (2020-08-25)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> versioninfo()
Julia Version 1.5.1
Commit 697e782ab8 (2020-08-25 20:08 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)

julia> Threads.@spawn begin
       jlm = @jimport java.lang.Math
       jcall(jlm, "sin", jdouble, (jdouble,), pi/2)
       end

Please submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.
Exception: EXCEPTION_ACCESS_VIOLATION at 0x708e3523 -- JVM_ResolveClass at C:\Program Files\Java\jre1.8.0_181\bin\server\jvm.dll (unknown line)
in expression starting at none:0
JVM_ResolveClass at C:\Program Files\Java\jre1.8.0_181\bin\server\jvm.dll (unknown line)
unknown function (ip: AA0000000006F755)
Allocations: 14080219 (Pool: 14076404; Big: 3815); GC: 14
mkitti commented 3 years ago

Did you set the environmental variable JULIA_COPY_STACKS=1 ?

mkitti commented 3 years ago

Oh, you're on Windows... hmm.

Also we may need to do something with AttachCurrentThread: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html

mkitti commented 3 years ago

I'm going to drop some hints here. Essentially to get this to work, you'll have to tap the lower level JNI interface in JavaCall: JavaCall.JNI.

using JavaCall
JavaCall.init()

# The following is a pointer to the AttachCurrentThread function of the JVM. Use ccall or @ccall to call that.
JavaCall.JNI.jvmfunc[].AttachCurrentThread

The C definition of that is

jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

The call to that is going to look a lot like https://github.com/JuliaInterop/JavaCall.jl/blob/master/src/JNI.jl#L145-L146

So something like:

        using JavaCall.JNI
        import JavaCall.JNI: JavaVMInitArgs
        ppenv_this_thread = Ref(Ptr{JNIEnv}(C_NULL))
        ppjvm = JavaCall.JNI.ppjvm
        res < 0 && throw(JavaCall.JNI.JNIError("Unable to initialise Java VM: $(res)"))
        JavaCall.JNI.ppenv[] = ppenv_this_thread[]

When using JavaCall from a new thread:

    empty!(JavaCall._jmc_cache) # You might need to do @jimport anew
    JavaCall.JNI.ppenv[] = ppenv_this_thread[]

There are synchronization issues there. The real solution is to to figure out the new thread specific ppenv into the individual JNI calls:

GetVersion(penv=ppenv[]) =
  ccall(jniref[].GetVersion, jint, (Ptr{JNIEnv},), penv)

DefineClass(name::AnyString, loader::jobject_arg, buf::Array{jbyte,1}, len::Integer, penv=ppenv[]) =
  ccall(jniref[].DefineClass, jclass, (Ptr{JNIEnv}, Cstring, jobject, Ptr{jbyte}, jsize,), penv, name, loader, buf, len)
[...]
mkitti commented 3 years ago

I started an experimental branch to push multithreading forwards on Linux / MacOS: https://github.com/JuliaInterop/JavaCall.jl/tree/multithreading