use std::{
cell::RefCell,
ops::{Deref, DerefMut},
ptr,
sync::atomic::{AtomicUsize, Ordering},
thread::{current, Thread},
};
use log::{debug, error};
use crate::{errors::*, sys, JNIEnv};
#[cfg(feature = "invocation")]
use {
crate::InitArgs,
std::os::raw::c_void,
std::{ffi::OsStr, path::PathBuf},
};
#[repr(transparent)]
#[derive(Debug)]
pub struct JavaVM(*mut sys::JavaVM);
unsafe impl Send for JavaVM {}
unsafe impl Sync for JavaVM {}
impl JavaVM {
#[cfg(feature = "invocation")]
pub fn new(args: InitArgs) -> StartJvmResult<Self> {
Self::with_libjvm(args, || {
Ok([
java_locator::locate_jvm_dyn_library()
.map_err(StartJvmError::NotFound)?
.as_str(),
java_locator::get_jvm_dyn_lib_file_name(),
]
.iter()
.collect::<PathBuf>())
})
}
#[cfg(feature = "invocation")]
pub fn with_libjvm<P: AsRef<OsStr>>(
args: InitArgs,
libjvm_path: impl FnOnce() -> StartJvmResult<P>,
) -> StartJvmResult<Self> {
let libjvm_path = libjvm_path()?;
let libjvm_path_string = libjvm_path.as_ref().to_string_lossy().into_owned();
let libjvm = match unsafe { libloading::Library::new(libjvm_path.as_ref()) } {
Ok(ok) => ok,
Err(error) => return Err(StartJvmError::LoadError(libjvm_path_string, error)),
};
unsafe {
let create_fn = libjvm
.get(b"JNI_CreateJavaVM\0")
.map_err(|error| StartJvmError::LoadError(libjvm_path_string.to_owned(), error))?;
Self::with_create_fn_ptr(args, *create_fn).map_err(StartJvmError::Create)
}
}
#[cfg(feature = "invocation")]
unsafe fn with_create_fn_ptr(
args: InitArgs,
create_fn_ptr: unsafe extern "system" fn(
pvm: *mut *mut sys::JavaVM,
penv: *mut *mut c_void,
args: *mut c_void,
) -> sys::jint,
) -> Result<Self> {
let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut();
let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut();
jni_error_code_to_result(create_fn_ptr(
&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)
}
pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Result<Self> {
non_null!(ptr, "from_raw ptr argument");
Ok(JavaVM(ptr))
}
pub fn get_java_vm_pointer(&self) -> *mut sys::JavaVM {
self.0
}
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),
}
}
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))
}
}
}
pub unsafe fn detach_current_thread(&self) {
InternalAttachGuard::clear_tls();
}
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),
}
}
pub fn threads_attached(&self) -> usize {
ATTACHED_THREADS.load(Ordering::SeqCst)
}
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)
}
}
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) }
}
pub unsafe fn destroy(&self) -> Result<()> {
unsafe {
let res = java_vm_unchecked!(self.0, DestroyJavaVM);
jni_error_code_to_result(res)
}
}
}
thread_local! {
static THREAD_ATTACH_GUARD: RefCell<Option<InternalAttachGuard>> = RefCell::new(None)
}
static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0);
pub struct AttachGuard<'local> {
env: JNIEnv<'local>,
should_detach: bool,
}
impl<'local> AttachGuard<'local> {
fn new(env: JNIEnv<'local>) -> Self {
Self {
env,
should_detach: true,
}
}
fn new_nested(env: JNIEnv<'local>) -> Self {
Self {
env,
should_detach: false,
}
}
}
impl<'local> Deref for AttachGuard<'local> {
type Target = JNIEnv<'local>;
fn deref(&self) -> &Self::Target {
&self.env
}
}
impl<'local> DerefMut for AttachGuard<'local> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.env
}
}
impl<'local> Drop for AttachGuard<'local> {
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,
thread: Thread,
}
impl InternalAttachGuard {
fn new(java_vm: *mut sys::JavaVM) -> Self {
Self {
java_vm,
thread: current(),
}
}
fn fill_tls(guard: InternalAttachGuard) {
THREAD_ATTACH_GUARD.with(move |f| {
*f.borrow_mut() = Some(guard);
});
}
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",
self.thread.name().unwrap_or_default(),
self.thread.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",
self.thread.name().unwrap_or_default(),
self.thread.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",
self.thread.name().unwrap_or_default(),
self.thread.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,
self.thread.name().unwrap_or_default(),
self.thread.id(),
);
}
}
}