• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::{
2     cell::RefCell,
3     ops::Deref,
4     ptr,
5     sync::atomic::{AtomicUsize, Ordering},
6     thread::current,
7 };
8 
9 use log::{debug, error};
10 
11 use crate::{errors::*, sys, JNIEnv};
12 
13 #[cfg(feature = "invocation")]
14 use crate::InitArgs;
15 use std::thread::Thread;
16 
17 /// The Java VM, providing [Invocation API][invocation-api] support.
18 ///
19 /// The JavaVM can be obtained either via [`JNIEnv#get_java_vm`][get-vm] in an already attached
20 /// thread, or it can be [launched](#launching-jvm-from-rust) from Rust via `JavaVM#new`.
21 ///
22 /// ## Attaching Native Threads
23 ///
24 /// A native thread must «attach» itself to be able to call Java methods outside of a native Java
25 /// method. This library provides two modes of attachment, each ensuring the thread is promptly
26 /// detached:
27 /// * A scoped attachment with [`attach_current_thread`][act].
28 ///   The thread will automatically detach itself once the returned guard is dropped.
29 /// * A permanent attachment with [`attach_current_thread_permanently`][actp]
30 ///   or [`attach_current_thread_as_daemon`][actd].
31 ///   The thread will automatically detach itself before it terminates.
32 ///
33 /// As attachment and detachment of a thread is an expensive operation, the scoped attachment
34 /// shall be used if happens infrequently. If you have an undefined scope where you need
35 /// to use `JNIEnv` and cannot keep the `AttachGuard`, consider attaching the thread
36 /// permanently.
37 ///
38 /// ### Local Reference Management
39 ///
40 /// Remember that the native thread attached to the VM **must** manage the local references
41 /// properly, i.e., do not allocate an excessive number of references and release them promptly
42 /// when they are no longer needed to enable the GC to collect them. A common approach is to use
43 /// an appropriately-sized local frame for larger code fragments
44 /// (see [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) and [Executor](#executor))
45 /// and [auto locals](struct.JNIEnv.html#method.auto_local) in loops.
46 ///
47 /// See also the [JNI specification][spec-references] for details on referencing Java objects.
48 ///
49 /// ### Executor
50 ///
51 /// Jni-rs provides an [`Executor`](struct.Executor.html) — a helper struct that allows to
52 /// execute a closure with `JNIEnv`. It combines the performance benefits of permanent attaches
53 /// *and* automatic local reference management. Prefer it to manual permanent attaches if
54 /// they happen in various parts of the code to reduce the burden of local reference management.
55 ///
56 /// ## Launching JVM from Rust
57 ///
58 /// To [launch][launch-vm] a JVM from a native process, enable the `invocation` feature
59 /// in the Cargo.toml:
60 ///
61 /// ```toml
62 /// jni = { version = "0.20.0", features = ["invocation"] }
63 /// ```
64 ///
65 /// The application will require linking to the dynamic `jvm` library, which is distributed
66 /// with the JVM, and allow to use `JavaVM#new`:
67 ///
68 /// ```rust
69 /// # use jni::errors;
70 /// # //
71 /// # fn main() -> errors::Result<()> {
72 /// # // Ignore this test without invocation feature, so that simple `cargo test` works
73 /// # #[cfg(feature = "invocation")] {
74 /// # //
75 /// # use jni::{AttachGuard, objects::JValue, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, sys::jint};
76 /// # //
77 /// // Build the VM properties
78 /// let jvm_args = InitArgsBuilder::new()
79 ///           // Pass the JNI API version (default is 8)
80 ///           .version(JNIVersion::V8)
81 ///           // You can additionally pass any JVM options (standard, like a system property,
82 ///           // or VM-specific).
83 ///           // Here we enable some extra JNI checks useful during development
84 ///           .option("-Xcheck:jni")
85 ///           .build()
86 ///           .unwrap();
87 ///
88 /// // Create a new VM
89 /// let jvm = JavaVM::new(jvm_args)?;
90 ///
91 /// // Attach the current thread to call into Java — see extra options in
92 /// // "Attaching Native Threads" section.
93 /// //
94 /// // This method returns the guard that will detach the current thread when dropped,
95 /// // also freeing any local references created in it
96 /// let env = jvm.attach_current_thread()?;
97 ///
98 /// // Call Java Math#abs(-10)
99 /// let x = JValue::from(-10);
100 /// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?
101 ///   .i()?;
102 ///
103 /// assert_eq!(val, 10);
104 ///
105 /// # }
106 /// # Ok(()) }
107 /// ```
108 ///
109 /// During build time, the JVM installation path is determined:
110 /// 1. By `JAVA_HOME` environment variable, if it is set.
111 /// 2. Otherwise — from `java` output.
112 ///
113 /// It is recommended to set `JAVA_HOME` to have reproducible builds,
114 /// especially, in case of multiple VMs installed.
115 ///
116 /// At application run time, you must specify the path
117 /// to the `jvm` library so that the loader can locate it.
118 /// * On **Windows**, append the path to `jvm.dll` to `PATH` environment variable.
119 /// * On **MacOS**, append the path to `libjvm.dylib` to `LD_LIBRARY_PATH` environment variable.
120 /// * On **Linux**, append the path to `libjvm.so` to `LD_LIBRARY_PATH` environment variable.
121 ///
122 /// The exact relative path to `jvm` library is version-specific.
123 ///
124 /// For more information on linking — see documentation
125 /// in [build.rs](https://github.com/jni-rs/jni-rs/tree/master/build.rs).
126 ///
127 /// [invocation-api]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html
128 /// [get-vm]: struct.JNIEnv.html#method.get_java_vm
129 /// [launch-vm]: struct.JavaVM.html#method.new
130 /// [act]: struct.JavaVM.html#method.attach_current_thread
131 /// [actp]: struct.JavaVM.html#method.attach_current_thread_permanently
132 /// [actd]: struct.JavaVM.html#method.attach_current_thread_as_daemon
133 /// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects
134 #[repr(transparent)]
135 #[derive(Debug)]
136 pub struct JavaVM(*mut sys::JavaVM);
137 
138 unsafe impl Send for JavaVM {}
139 unsafe impl Sync for JavaVM {}
140 
141 impl JavaVM {
142     /// Launch a new JavaVM using the provided init args.
143     ///
144     /// Unlike original JNI API, the main thread (the thread from which this method is called) will
145     /// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer
146     /// to [Attaching Native Threads section](#attaching-native-threads)).
147     ///
148     /// *This API requires "invocation" feature to be enabled,
149     /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
150     #[cfg(feature = "invocation")]
new(args: InitArgs) -> Result<Self>151     pub fn new(args: InitArgs) -> Result<Self> {
152         use std::os::raw::c_void;
153 
154         let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut();
155         let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut();
156 
157         unsafe {
158             jni_error_code_to_result(sys::JNI_CreateJavaVM(
159                 &mut ptr as *mut _,
160                 &mut env as *mut *mut sys::JNIEnv as *mut *mut c_void,
161                 args.inner_ptr(),
162             ))?;
163 
164             let vm = Self::from_raw(ptr)?;
165             java_vm_unchecked!(vm.0, DetachCurrentThread);
166 
167             Ok(vm)
168         }
169     }
170 
171     /// Create a JavaVM from a raw pointer.
172     ///
173     /// # Safety
174     ///
175     /// Expects a valid pointer retrieved from the `JNI_CreateJavaVM` JNI function. Only does null check.
from_raw(ptr: *mut sys::JavaVM) -> Result<Self>176     pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Result<Self> {
177         non_null!(ptr, "from_raw ptr argument");
178         Ok(JavaVM(ptr))
179     }
180 
181     /// Returns underlying `sys::JavaVM` interface.
get_java_vm_pointer(&self) -> *mut sys::JavaVM182     pub fn get_java_vm_pointer(&self) -> *mut sys::JavaVM {
183         self.0
184     }
185 
186     /// Attaches the current thread to the JVM. Calling this for a thread that is already attached
187     /// is a no-op.
188     ///
189     /// The thread will detach itself automatically when it exits.
190     ///
191     /// Attached threads [block JVM exit][block]. If it is not desirable — consider using
192     /// [`attach_current_thread_as_daemon`][attach-as-daemon].
193     ///
194     /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm
195     /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon
attach_current_thread_permanently(&self) -> Result<JNIEnv>196     pub fn attach_current_thread_permanently(&self) -> Result<JNIEnv> {
197         match self.get_env() {
198             Ok(env) => Ok(env),
199             Err(_) => self.attach_current_thread_impl(ThreadType::Normal),
200         }
201     }
202 
203     /// Attaches the current thread to the Java VM. The returned `AttachGuard`
204     /// can be dereferenced to a `JNIEnv` and automatically detaches the thread
205     /// when dropped. Calling this in a thread that is already attached is a no-op, and
206     /// will neither change its daemon status nor prematurely detach it.
207     ///
208     /// Attached threads [block JVM exit][block].
209     ///
210     /// Attaching and detaching a thread is an expensive operation. If you use it frequently
211     /// in the same threads, consider either [attaching them permanently][attach-as-daemon],
212     /// or, if the scope where you need the `JNIEnv` is well-defined, keeping the returned guard.
213     ///
214     /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm
215     /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon
attach_current_thread(&self) -> Result<AttachGuard>216     pub fn attach_current_thread(&self) -> Result<AttachGuard> {
217         match self.get_env() {
218             Ok(env) => Ok(AttachGuard::new_nested(env)),
219             Err(_) => {
220                 let env = self.attach_current_thread_impl(ThreadType::Normal)?;
221                 Ok(AttachGuard::new(env))
222             }
223         }
224     }
225 
226     /// Detaches current thread from the JVM. This operation is _rarely_ appropriate to use,
227     /// because the attachment methods [ensure](#attaching-native-threads) that the thread is
228     /// promptly detached.
229     ///
230     /// Detaching a non-attached thread is a no-op.
231     ///
232     /// __Any existing `JNIEnv`s and `AttachGuard`s created in the calling thread
233     /// will be invalidated after this method completes. It is the__ caller’s __responsibility
234     /// to ensure that no JNI calls are subsequently performed on these objects.__
235     /// Failure to do so will result in unspecified errors, possibly, the process crash.
236     ///
237     /// Given some care is exercised, this method can be used to detach permanently attached
238     /// threads _before_ they exit (when automatic detachment occurs). However, it is
239     /// never appropriate to use it with the scoped attachment (`attach_current_thread`).
240     // This method is hidden because it is almost never needed and its use requires some
241     // extra care. Its status might be reconsidered if we learn of any use cases that require it.
242     #[doc(hidden)]
detach_current_thread(&self)243     pub fn detach_current_thread(&self) {
244         InternalAttachGuard::clear_tls();
245     }
246 
247     /// Attaches the current thread to the Java VM as a _daemon_. Calling this in a thread
248     /// that is already attached is a no-op, and will not change its status to a daemon thread.
249     ///
250     /// The thread will detach itself automatically when it exits.
attach_current_thread_as_daemon(&self) -> Result<JNIEnv>251     pub fn attach_current_thread_as_daemon(&self) -> Result<JNIEnv> {
252         match self.get_env() {
253             Ok(env) => Ok(env),
254             Err(_) => self.attach_current_thread_impl(ThreadType::Daemon),
255         }
256     }
257 
258     /// Returns the current number of threads attached to the JVM.
259     ///
260     /// This method is provided mostly for diagnostic purposes.
threads_attached(&self) -> usize261     pub fn threads_attached(&self) -> usize {
262         ATTACHED_THREADS.load(Ordering::SeqCst)
263     }
264 
265     /// Get the `JNIEnv` associated with the current thread, or
266     /// `ErrorKind::Detached`
267     /// if the current thread is not attached to the java VM.
get_env(&self) -> Result<JNIEnv>268     pub fn get_env(&self) -> Result<JNIEnv> {
269         let mut ptr = ptr::null_mut();
270         unsafe {
271             let res = java_vm_unchecked!(self.0, GetEnv, &mut ptr, sys::JNI_VERSION_1_1);
272             jni_error_code_to_result(res)?;
273 
274             JNIEnv::from_raw(ptr as *mut sys::JNIEnv)
275         }
276     }
277 
278     /// Creates `InternalAttachGuard` and attaches current thread.
attach_current_thread_impl(&self, thread_type: ThreadType) -> Result<JNIEnv>279     fn attach_current_thread_impl(&self, thread_type: ThreadType) -> Result<JNIEnv> {
280         let guard = InternalAttachGuard::new(self.get_java_vm_pointer());
281         let env_ptr = unsafe {
282             if thread_type == ThreadType::Daemon {
283                 guard.attach_current_thread_as_daemon()?
284             } else {
285                 guard.attach_current_thread()?
286             }
287         };
288 
289         InternalAttachGuard::fill_tls(guard);
290 
291         unsafe { JNIEnv::from_raw(env_ptr as *mut sys::JNIEnv) }
292     }
293 }
294 
295 thread_local! {
296     static THREAD_ATTACH_GUARD: RefCell<Option<InternalAttachGuard>> = RefCell::new(None)
297 }
298 
299 static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0);
300 
301 /// A RAII implementation of scoped guard which detaches the current thread
302 /// when dropped. The attached `JNIEnv` can be accessed through this guard
303 /// via its `Deref` implementation.
304 pub struct AttachGuard<'a> {
305     env: JNIEnv<'a>,
306     should_detach: bool,
307 }
308 
309 impl<'a> AttachGuard<'a> {
310     /// AttachGuard created with this method will detach current thread on drop
new(env: JNIEnv<'a>) -> Self311     fn new(env: JNIEnv<'a>) -> Self {
312         Self {
313             env,
314             should_detach: true,
315         }
316     }
317 
318     /// AttachGuard created with this method will not detach current thread on drop, which is
319     /// the case for nested attaches.
new_nested(env: JNIEnv<'a>) -> Self320     fn new_nested(env: JNIEnv<'a>) -> Self {
321         Self {
322             env,
323             should_detach: false,
324         }
325     }
326 }
327 
328 impl<'a> Deref for AttachGuard<'a> {
329     type Target = JNIEnv<'a>;
330 
deref(&self) -> &Self::Target331     fn deref(&self) -> &Self::Target {
332         &self.env
333     }
334 }
335 
336 impl<'a> Drop for AttachGuard<'a> {
drop(&mut self)337     fn drop(&mut self) {
338         if self.should_detach {
339             InternalAttachGuard::clear_tls();
340         }
341     }
342 }
343 
344 #[derive(PartialEq)]
345 enum ThreadType {
346     Normal,
347     Daemon,
348 }
349 
350 #[derive(Debug)]
351 struct InternalAttachGuard {
352     java_vm: *mut sys::JavaVM,
353     /// A call std::thread::current() function can panic in case the local data has been destroyed
354     /// before the thead local variables. The possibility of this happening depends on the platform
355     /// implementation of the crate::sys_common::thread_local_dtor::register_dtor_fallback.
356     /// The InternalAttachGuard is a thread-local vairable, so capture the thread meta-data
357     /// during creation
358     thread: Thread,
359 }
360 
361 impl InternalAttachGuard {
new(java_vm: *mut sys::JavaVM) -> Self362     fn new(java_vm: *mut sys::JavaVM) -> Self {
363         Self {
364             java_vm,
365             thread: current(),
366         }
367     }
368 
369     /// Stores guard in thread local storage.
fill_tls(guard: InternalAttachGuard)370     fn fill_tls(guard: InternalAttachGuard) {
371         THREAD_ATTACH_GUARD.with(move |f| {
372             *f.borrow_mut() = Some(guard);
373         });
374     }
375 
376     /// Clears thread local storage, dropping the InternalAttachGuard and causing detach of
377     /// the current thread.
clear_tls()378     fn clear_tls() {
379         THREAD_ATTACH_GUARD.with(move |f| {
380             *f.borrow_mut() = None;
381         });
382     }
383 
attach_current_thread(&self) -> Result<*mut sys::JNIEnv>384     unsafe fn attach_current_thread(&self) -> Result<*mut sys::JNIEnv> {
385         let mut env_ptr = ptr::null_mut();
386         let res = java_vm_unchecked!(
387             self.java_vm,
388             AttachCurrentThread,
389             &mut env_ptr,
390             ptr::null_mut()
391         );
392         jni_error_code_to_result(res)?;
393 
394         ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst);
395 
396         debug!(
397             "Attached thread {} ({:?}). {} threads attached",
398             self.thread.name().unwrap_or_default(),
399             self.thread.id(),
400             ATTACHED_THREADS.load(Ordering::SeqCst)
401         );
402 
403         Ok(env_ptr as *mut sys::JNIEnv)
404     }
405 
attach_current_thread_as_daemon(&self) -> Result<*mut sys::JNIEnv>406     unsafe fn attach_current_thread_as_daemon(&self) -> Result<*mut sys::JNIEnv> {
407         let mut env_ptr = ptr::null_mut();
408         let res = java_vm_unchecked!(
409             self.java_vm,
410             AttachCurrentThreadAsDaemon,
411             &mut env_ptr,
412             ptr::null_mut()
413         );
414         jni_error_code_to_result(res)?;
415 
416         ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst);
417 
418         debug!(
419             "Attached daemon thread {} ({:?}). {} threads attached",
420             self.thread.name().unwrap_or_default(),
421             self.thread.id(),
422             ATTACHED_THREADS.load(Ordering::SeqCst)
423         );
424 
425         Ok(env_ptr as *mut sys::JNIEnv)
426     }
427 
detach(&mut self) -> Result<()>428     fn detach(&mut self) -> Result<()> {
429         unsafe {
430             java_vm_unchecked!(self.java_vm, DetachCurrentThread);
431         }
432         ATTACHED_THREADS.fetch_sub(1, Ordering::SeqCst);
433         debug!(
434             "Detached thread {} ({:?}). {} threads remain attached",
435             self.thread.name().unwrap_or_default(),
436             self.thread.id(),
437             ATTACHED_THREADS.load(Ordering::SeqCst)
438         );
439 
440         Ok(())
441     }
442 }
443 
444 impl Drop for InternalAttachGuard {
drop(&mut self)445     fn drop(&mut self) {
446         if let Err(e) = self.detach() {
447             error!(
448                 "Error detaching current thread: {:#?}\nThread {} id={:?}",
449                 e,
450                 self.thread.name().unwrap_or_default(),
451                 self.thread.id(),
452             );
453         }
454     }
455 }
456