j4rs/async_api/
mod.rs

1// Copyright 2023 astonbitecode
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::ptr;
16
17use jni_sys::{jobject, jstring};
18
19use futures::channel::oneshot;
20
21use crate::errors::opt_to_res;
22use crate::{cache, errors, jni_utils, Instance, InvocationArg, Jvm};
23
24use super::logger::debug;
25
26impl Jvm {
27    /// Invokes the method `method_name` of a created `Instance` asynchronously, passing an array of `InvocationArg`s.
28    /// It returns an `Instance` as the result of the invocation.
29    pub async fn invoke_async(
30        &self,
31        instance: &Instance,
32        method_name: &str,
33        inv_args: &[InvocationArg],
34    ) -> errors::Result<Instance> {
35        debug(&format!(
36            "Asynchronously invoking method {} of class {} using {} arguments",
37            method_name,
38            instance.class_name,
39            inv_args.len()
40        ));
41        // Create the channel
42        let (sender, rx) = oneshot::channel::<errors::Result<Instance>>();
43        unsafe {
44            Self::handle_channel_sender(self, sender, instance, method_name, inv_args)?;
45        }
46        // Create and return the Instance
47        let instance = rx.await?;
48        Self::do_return(self.jni_env, instance)?
49    }
50
51    /// Invokes the method `method_name` of a created `Instance` asynchronously, passing an array of `InvocationArg`s.
52    /// It returns an `Instance` as the result of the invocation.
53    /// 
54    /// 
55    /// `Instance`s  are `Send` and can be safely sent to other threads. However, because of [Send Approximation](https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html), the `Future` returned by `invoke_async` is _not_ `Send`, even if it just contains an `Instance`. This is because the `Jvm` is being captured by the `async` call as well and the `Jvm` is __not__ `Send`.
56    /// 
57    /// In order to have a `Future<Instance>` that __is__ `Send`, the `Jvm::invoke_into_sendable_async` can be used. This function does not get a `Jvm` as argument; it creates one internally when needed and applies some scoping workarounds in order to achieve returning a `Future<Instance>` which is also `Send`.
58    pub async fn invoke_into_sendable_async(
59        instance: Instance,
60        method_name: String,
61        inv_args: Vec<InvocationArg>,
62    ) -> errors::Result<Instance> {
63        debug(&format!(
64            "Asynchronously invoking (2) method {} of class {} using {} arguments",
65            method_name,
66            instance.class_name,
67            inv_args.len()
68        ));
69        // Create the channel
70        let (sender, rx) = oneshot::channel::<errors::Result<Instance>>();
71        unsafe {
72            let s = Jvm::attach_thread()?;
73            Self::handle_channel_sender(&s, sender, &instance, &method_name, inv_args.as_ref())?;
74            drop(s);
75        }
76
77        // Create and return the Instance
78        let instance = rx.await?;
79        let new_jvm = Jvm::attach_thread()?;
80        let new_jni_env = new_jvm.jni_env;
81        Self::do_return(new_jni_env, instance)?
82    }
83
84    unsafe fn handle_channel_sender(s: &Jvm, sender: oneshot::Sender<errors::Result<Instance>>, instance: &Instance, method_name: &str, inv_args: &[InvocationArg]) -> errors::Result<()> {
85            let tx = Box::new(sender);
86            // First argument: the address of the channel Sender
87            let raw_ptr = Box::into_raw(tx);
88            // Find the address of tx
89            let address_string = format!("{:p}", raw_ptr);
90            let address = i64::from_str_radix(&address_string[2..], 16).unwrap();
91
92            // Second argument: create a jstring to pass as argument for the method_name
93            let method_name_jstring: jstring =
94                jni_utils::global_jobject_from_str(method_name, s.jni_env)?;
95
96            // Rest of the arguments: Create a new objectarray of class InvocationArg
97            let size = inv_args.len() as i32;
98            let array_ptr = {
99                let j = (opt_to_res(cache::get_jni_new_object_array())?)(
100                    s.jni_env,
101                    size,
102                    cache::get_invocation_arg_class()?,
103                    ptr::null_mut(),
104                );
105                jni_utils::create_global_ref_from_local_ref(j, s.jni_env)?
106            };
107            let mut inv_arg_jobjects: Vec<jobject> = Vec::with_capacity(size as usize);
108
109            // Rest of the arguments: populate the array
110            for i in 0..size {
111                // Create an InvocationArg Java Object
112                let inv_arg_java =
113                    inv_args[i as usize].as_java_ptr_with_global_ref(s.jni_env)?;
114                // Set it in the array
115                (opt_to_res(cache::get_jni_set_object_array_element())?)(
116                    s.jni_env,
117                    array_ptr,
118                    i,
119                    inv_arg_java,
120                );
121                inv_arg_jobjects.push(inv_arg_java);
122            }
123
124            // Call the method of the instance
125            (opt_to_res(cache::get_jni_call_void_method())?)(
126                s.jni_env,
127                instance.jinstance,
128                cache::get_invoke_async_method()?,
129                address,
130                method_name_jstring,
131                array_ptr,
132            );
133
134            // Check for exceptions before creating the globalref
135            Self::do_return(s.jni_env, ())?;
136
137            // Prevent memory leaks from the created local references
138            for inv_arg_jobject in inv_arg_jobjects {
139                jni_utils::delete_java_ref(s.jni_env, inv_arg_jobject);
140            }
141            jni_utils::delete_java_ref(s.jni_env, array_ptr);
142            jni_utils::delete_java_ref(s.jni_env, method_name_jstring);
143            Ok(())
144    }
145}
146
147#[cfg(test)]
148mod api_unit_tests {
149    use super::*;
150    use crate::lib_unit_tests::create_tests_jvm;
151    use futures::Future;
152    use tokio;
153
154    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
155    async fn invoke_async_success_w_tokio() -> errors::Result<()> {
156        let s_test = "j4rs_rust";
157        let jvm = create_tests_jvm()?;
158        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
159        let instance = jvm
160            .invoke_async(
161                &my_test,
162                "getStringWithFuture",
163                &[InvocationArg::try_from(s_test)?],
164            )
165            .await?;
166        let string: String = jvm.to_rust(instance)?;
167        assert_eq!(s_test, string);
168        Ok(())
169    }
170
171    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
172    async fn invoke_async_failure_w_tokio() -> errors::Result<()> {
173        let s_test = "Boom!";
174        let jvm = create_tests_jvm()?;
175        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
176        let instance_result = jvm
177            .invoke_async(
178                &my_test,
179                "getErrorWithFuture",
180                &[InvocationArg::try_from(s_test)?],
181            )
182            .await;
183        assert!(instance_result.is_err());
184        let error = instance_result.err().unwrap();
185        println!("{}", error);
186        Ok(())
187    }
188
189    #[async_std::test]
190    async fn invoke_async_success_w_async_std() -> errors::Result<()> {
191        let s_test = "j4rs_rust";
192        let jvm = create_tests_jvm()?;
193        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
194        let instance = jvm
195            .invoke_async(
196                &my_test,
197                "getStringWithFuture",
198                &[InvocationArg::try_from(s_test)?],
199            )
200            .await?;
201        let string: String = jvm.to_rust(instance)?;
202        assert_eq!(s_test, string);
203        Ok(())
204    }
205
206    #[async_std::test]
207    async fn invoke_async_failure_w_async_std() -> errors::Result<()> {
208        let s_test = "Boom!";
209        let jvm = create_tests_jvm()?;
210        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
211        let instance_result = jvm
212            .invoke_async(
213                &my_test,
214                "getErrorWithFuture",
215                &[InvocationArg::try_from(s_test)?],
216            )
217            .await;
218        assert!(instance_result.is_err());
219        let error = instance_result.err().unwrap();
220        println!("{}", error);
221        Ok(())
222    }
223
224    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
225    async fn invoke_async_and_reuse_instance() -> errors::Result<()> {
226        let s_test1 = "j4rs_rust1";
227        let s_test2 = "j4rs_rust2";
228        let jvm = create_tests_jvm()?;
229        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
230        let instance1 = jvm
231            .invoke_async(
232                &my_test,
233                "getStringWithFuture",
234                &[InvocationArg::try_from(s_test1)?],
235            )
236            .await?;
237        let instance2 = jvm
238            .invoke_async(
239                &my_test,
240                "getStringWithFuture",
241                &[InvocationArg::try_from(s_test2)?],
242            )
243            .await?;
244        let string1: String = jvm.to_rust(instance1)?;
245        let string2: String = jvm.to_rust(instance2)?;
246        assert_eq!(s_test1, string1);
247        assert_eq!(s_test2, string2);
248        Ok(())
249    }
250
251    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
252    async fn invoke_static_async() -> errors::Result<()> {
253        let s_test = "j4rs_rust";
254        let jvm = create_tests_jvm()?;
255        let my_test = jvm.static_class("org.astonbitecode.j4rs.tests.MyTest")?;
256        let instance = jvm
257            .invoke_async(
258                &my_test,
259                "getErrorWithFutureStatic",
260                &[InvocationArg::try_from(s_test)?],
261            )
262            .await?;
263        let string: String = jvm.to_rust(instance)?;
264        assert_eq!(s_test, string);
265        Ok(())
266    }
267
268    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
269    async fn invoke_async_error_before_executing_async() -> errors::Result<()> {
270        let s_test = "j4rs_rust";
271        let jvm = create_tests_jvm()?;
272        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
273        let instance_result = jvm
274            .invoke_async(&my_test, "echo", &[InvocationArg::try_from(s_test)?])
275            .await;
276        assert!(instance_result.is_err());
277        Ok(())
278    }
279
280    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
281    async fn invoke_void_future() -> errors::Result<()> {
282        let s_test = "j4rs_rust";
283        let jvm = create_tests_jvm()?;
284        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
285        let instance_res = jvm
286            .invoke_async(
287                &my_test,
288                "executeVoidFuture",
289                &[InvocationArg::try_from(s_test)?],
290            )
291            .await;
292        assert!(instance_res.is_ok());
293        Ok(())
294    }
295
296    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
297    async fn invoke_into_sendable_async_success() -> errors::Result<()> {
298        let s_test = "j4rs_rust";
299        let jvm = create_tests_jvm()?;
300        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
301        let instance = Jvm::invoke_into_sendable_async(
302                my_test,
303                "getStringWithFuture".to_string(),
304                vec![InvocationArg::try_from(s_test)?],
305            )
306            .await?;
307        let string: String = jvm.to_rust(instance)?;
308        assert_eq!(s_test, string);
309        Ok(())
310    }
311
312    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
313    async fn future_is_send() -> errors::Result<()> {
314        let s_test = "j4rs_rust";
315        let jvm = create_tests_jvm()?;
316        let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
317        let f = Jvm::invoke_into_sendable_async(
318            my_test,
319            "executeVoidFuture".to_string(),
320            vec![InvocationArg::try_from(s_test)?],
321        );
322        check_send(f);
323        Ok(())
324    }
325
326    fn check_send<F:Future>(_:F) where F:Send + 'static {}
327
328    // #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
329    async fn _memory_leaks_invoke_async_instances() -> errors::Result<()> {
330        let jvm = create_tests_jvm()?;
331        let instance = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty() as &[InvocationArg; 0])?;
332        for i in 0..100000000 {
333            if i % 100000 == 0 {
334                println!("{}", i);
335            }
336            let ia = InvocationArg::try_from(i.to_string())?;
337            let _s = jvm
338                .invoke_async(&instance, "getStringWithFuture", &[ia])
339                .await?;
340        }
341        Ok(())
342    }
343}