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