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;
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) -> 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)
}
}
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))
}
}
}
#[doc(hidden)]
pub 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) }
}
}
thread_local! {
static THREAD_ATTACH_GUARD: RefCell<Option<InternalAttachGuard>> = RefCell::new(None)
}
static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0);
pub struct AttachGuard<'a> {
env: JNIEnv<'a>,
should_detach: bool,
}
impl<'a> AttachGuard<'a> {
fn new(env: JNIEnv<'a>) -> Self {
Self {
env,
should_detach: true,
}
}
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 }
}
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",
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(),
);
}
}
}