timob / jnigi

Golang Java JNI library
BSD 2-Clause "Simplified" License
163 stars 44 forks source link

FATAL ERROR in native method: Using JNIEnv in non-Java thread #52

Closed yissatayev closed 3 years ago

yissatayev commented 3 years ago

Hi,

If I initialize JVM (load, create, etc.) in the main function, I can successfully execute a Java code without any problems. However, If I try to initialize JVM only once after the application has started instead of initializing for each call and call a Java code in handler functions, I get the following error:

FATAL ERROR in native method: Using JNIEnv in non-Java thread

Could you please help with what I am trying to achieve? Basically, I want to create an end-point in a Go application which will call some Java code each time request is received.

The sample code I am working with is here TinkJNIGo

timob commented 3 years ago

You need to call AttachCurrentThread on the *JVM. This returns a new *Env which can used in the current thread. And then call DetachCurrentThread when your done.

One way to do this so you don't have keep attaching threads is something like:

var funcChan = make(chan func(*jnigi.Env))

func RunOnJavaAttachedThread(f func(*jnigi.Env)) {
    funcChan <- f
}

// run with go keyword from main
func RunJavaThreadFuncs(jvm *jnigi.JVM) {
    env := jvm.AttachCurrentThread()
    for f := range funcChan {
        f(env)
    }
} 

/*
In end-point handler
RunOnJavaAttachedThread(func(env *jnigi.Env) {
   // run my java code here
})

*/

This means that bits of Java code will be only run one at a time. You could use sync.Pool if you want to handle more than one at a time.

One thing to note is AttachCurrentThread calls runtime.LockOSThread. I might change this in the future so that the program it self calls this.

timob commented 3 years ago

Any update on this? Hope this helped. Probably best to close this if this is no longer a problem.

yissatayev commented 3 years ago

Sorry for the late reply, I missed your first reply somehow. I just tried the suggested solution and getting FATAL ERROR in native method: Using JNIEnv in the wrong thread. Probably I am doing something wrong. Let me look into this a bit more and I'll come back to you. Thank you.

yissatayev commented 3 years ago

Hi @timob I was able to implement the approach you suggested. Everything works, but sometimes I receive the following warning messages:

WARNING: JNI local refs: 33, exceeds capacity: 32

After some more requests:

WARNING: JNI local refs: 66, exceeds capacity: 65

And this continues incrementing. Is that OK? Or is it potentially a problem?

timob commented 3 years ago

See EnsureLocalCapacity in JNI docs. Generally it's not problem, but if it's a long running service, you probably want to use DeleteLocalRef or Push/PopLocalFrame to free references to Java objects when you are done with them.

timob commented 3 years ago

Good to hear that code fixed the problem in the issue title 👍👍👍. Please open another issue if you're having a different problem too.