-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jvm cannot be sent between threads. How can I work with invoke_async? #103
Comments
As long as you do not use Unfortunately I cannot test it myself as I am currently on vacations, staying away from the keyboard.. |
It's probably due to tonic forcing the future to be Also found some discussion here: hyperium/tonic#844 |
Ah, I believe I see the problem... The This is fixable though and will deal with it in a few days. Thanks for the report, this is a nice finding! |
Awesome! Thanks for confirming! Looking forward to the fix. |
I took the time to test using async with tonic and unfortunately, there are more errors regarding
This was an over-optimistic statement because I took as granted that the issue was only because of the internal reuse of the However, there are more errors, with the most important being:
Even if the rest of the errors are somehow fixed, I guess we cannot do anything about this one... The reason is that by jni specification:
So, I believe by JNI definition, the |
Taking a step back though, I cannot fully understand why we get the compilation error, that is, why the block before For example, in the code snippet you provided, could the execution be stopped at line: let Ok(jvm) = Jvm::attach_thread()
else { return Err(Status::internal("attach_thread failed")) }; and then continue from line: let Ok(my_object) = jvm.invoke_static("com.example.MyObject", "getInstance", &Vec::<InvocationArg>::new())
else { return Err(Status::internal("invoke_static failed")) }; in another thread? I thought this execution is not interrupted... What do I miss? |
Yes, you are correct. I don't think the execution is interrupted after either I also thought about how to fix it. I have a strong feeling that we need to implement the future manually, instead of relying on |
It seems that with a bit of "scoping" and "moving" we can have the I implemented a function
According to the above, your example should compile like: #[tonic::async_trait]
impl MyApi for MyApiService {
async fn get(&self, request: Request<GetRequest>) -> Result<Response<GetResponse>, Status> {
let (my_object, args) = {
let Ok(jvm) = Jvm::attach_thread() else {
return Err(Status::internal("attach_thread failed"));
};
let Ok(my_object) = jvm.invoke_static(
"com.example.MyObject",
"getInstance",
&Vec::<InvocationArg>::new(),
) else {
return Err(Status::internal("invoke_static failed"));
};
let args: Vec<InvocationArg> = vec![
InvocationArg::try_from("a_string").unwrap(),
InvocationArg::try_from(3).unwrap(),
// ...
];
(my_object, args)
};
let _ = Jvm::invoke_into_sendable_async(my_object, "get".to_string(), args).await;
// ...
Ok(Response::new(GetResponse::default()))
}
} Thoughts:
|
Thanks for putting up the quick fix! I was thinking about manually creating the future so that even if we pass However, I think there is an issue in the implementation. I put up a PR here #104.
Yeah, if the compiler can deduce that the non-sendable variables are never used across await, it would make our life lot easier. It seems we have to do manual scoping today. The trick is also described in https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html. Sad :( In terms of performance, it might be Ok to attach the thread twice. I'm not sure how much penalty we will hit. I think we need some benchmarking to get some idea. However, I checked the Java part and I'm concerned about the use of Stream APIs. Based on my past experience, they are bad for GC and CPU. Given |
Sorry, I misread your comment. I saw that Self::do_return(new_jni_env, instance)?.map(|instance| (instance, inv_args)) It seems that currently scoping is the only way to make compiler believe that a non-sendable variable is no longer being used. I tried the following to reuse the Jvm from the current thread, but it didn't work. pub async fn invoke_into_sendable_async_consuming_self(
self,
instance: Instance,
method_name: String,
inv_args: Vec<InvocationArg>,
) -> errors::Result<Instance> {
debug(&format!(
"Asynchronously invoking (2) method {} of class {} using {} arguments",
method_name,
instance.class_name,
inv_args.len()
));
// Create the channel
let (sender, rx) = oneshot::channel::<errors::Result<Instance>>();
unsafe {
Self::handle_channel_sender(&self, sender, &instance, &method_name, inv_args.as_ref())?;
}
drop(self);
// Create and return the Instance
let instance = rx.await?;
let new_jvm = Jvm::attach_thread()?;
let new_jni_env = new_jvm.jni_env;
Self::do_return(new_jni_env, instance)?
} |
The However, even if we have references of Instances be Having said that, I believe that the api of So, if it works for you too and you can use the A bit out of context, but I think the |
Sounds good. Thanks for addressing the issue! Feel free to close it. |
BTW, when do you think you can release this new feature? |
There is no specific schedule. I can create a new release today or tomorrow. |
Hello,
Based on #72, Jvm cannot be sent between threads. In that case, how can I work with
invoke_async
? As soon as I callawait
on the future returned byinvoke_async
, I get the compilation error complaining that Jvm cannot be sent between threads. How can I work around it? Thanks!Sample error message:
Sample code snippet
The text was updated successfully, but these errors were encountered: