kud1ing / rucaja

Calling the JVM from Rust via JNI
https://docs.rs/rucaja
Apache License 2.0
31 stars 7 forks source link

Multi-thread use case #19

Open hongxuchen opened 6 years ago

hongxuchen commented 6 years ago

My code is in a multi-thread setting, inside each thread, there is a need to call jvm functions.

I find that the "jvm" instance itself cannot be constructed separately inside each thread, by loading the classpath with proper options. In this case, it will panic with a message:

 '`JNI_CreateJavaVM()` signaled an error: JVM exists already'` ...rucaja-0.4.0/src/jvm.rs:186:12

This alternative seems to be putting JVM construction code before spawning threads and then passing as parameters.

The problem is that I'm not quite sure whether this will cause data race internally at the JVM side.

Since I know little about JNI and would like to be merely a "user" of rucaja, Could you please advise?

kud1ing commented 6 years ago

My impression is that one can only have exactly one JVM in one process in total, and not sequentially: https://docs.rs/rucaja/0.4.2/rucaja/struct.Jvm.html#method.new

If i understand correctly, sharing the same JVM from different Rust threads should be safe, since we attach/detach using JvmAttachment (AttachCurrentThread() + DetachCurrentThread()). There still could be bugs, since i haven't tried what you are doing.

An alternative could be to use different Rust processes.

kud1ing commented 6 years ago

And thanks by the way for using Rucaja. I am interested in making it more usable, so your feedback is very appreciated.

kud1ing commented 6 years ago

I have no deep JNI knowledge myself. @fpoli, @Treyzania, @tupshin, @Dushistov do you have any experience regarding multithreading and JNI? Is the above approach sufficient?

fpoli commented 6 years ago

I'm also not an expert, but I just found that "according to docs, only one thread can use use JVM at a time". So, it should be possible by using a Mutex<Jvm> (or some other technique).

hongxuchen commented 6 years ago

@kud1ing and @fpoli Thanks so much for your help! I think I've to choose another approach.

treyzania commented 6 years ago

@kud1ing I'm a bit late to this party, but I would assume that we could restructure the APIs a bit so that we always return a Mutex<Jvm> or some preferably opaque type that makes sure that only one thread is actually interacting with the JVM at a time. This would allow us to automagically issue the call to DetachCurrentThread in the Drop implementation for whatever this type returns when we try to "attach" to the JVM, which would be super awesome!

Unless we want to make Jvm be !Send, which would fix the problem, but in a very shitty way. As I side note, I can't figure out why anybody would need !Send outside of wrapping code written in other languages (OpenGL contexts are per-thread, etc.) or maybe in a kernel.

EDIT: I'm now realizing that Jvm is naturally !Send because of how Rust auto-derives things. I'd need to think on it a bit as I am far from a JNI expert. Worst-case scenario if we can't find a reliable way to pass it between threads somehow would be to have a "JNI thread" that actually owns the JVM, that we can pass a closure to be queued to be executed (with std::sync::mspc or something like it), and then layer some abstractions on top of that to make it easy to simple things with. This is so suboptimal, but it's a worse-case scenario, honestly. The fact that the JVM can spawn its own threads that can potentially have references to things that we allocated makes things all kinds of complicated. So those are more concerns (perhaps translating using Box::into_raw and Box::from_raw?) for dealing with passing things into the JVM.