use std::cell::Cell;
use crate::{
raw::{EnvPtr, JvmPtr},
Error, Result,
};
thread_local! {
static STATE: Cell<State> = Cell::new(State::Detached);
}
#[derive(Debug, PartialEq, Eq)]
pub enum State {
InUse,
AttachedPermanently(EnvPtr<'static>),
Detached,
}
fn attached_or(
jvm: JvmPtr,
f: impl FnOnce() -> Result<AttachGuard>,
) -> Result<AttachGuard> {
STATE.with(|state| match state.replace(State::InUse) {
State::AttachedPermanently(env) => Ok(AttachGuard {
jvm,
env,
permanent: true,
}),
State::InUse => Err(Error::NestedUsage),
State::Detached => {
let result = f();
if result.is_err() {
state.set(State::Detached);
}
result
}
})
}
#[must_use = "remember to call `detach_from_jni_callback`"]
pub unsafe fn attach_from_jni_callback(env: EnvPtr<'_>) -> JniCallbackGuard<'_> {
let old_state = STATE.with(|state| {
let env: EnvPtr<'static> = unsafe { std::mem::transmute(env) };
state.replace(State::AttachedPermanently(env))
});
JniCallbackGuard { env, old_state }
}
pub struct JniCallbackGuard<'env> {
env: EnvPtr<'env>,
old_state: State,
}
impl Drop for JniCallbackGuard<'_> {
fn drop(&mut self) {
STATE.with(|state| {
let old_state = std::mem::replace(&mut self.old_state, State::InUse);
let jni_state = state.replace(old_state);
let env: EnvPtr<'static> = unsafe { std::mem::transmute(self.env) };
assert!(
jni_state == State::AttachedPermanently(env),
"invalid prior state `{jni_state:?}`"
);
});
}
}
pub fn attach_permanently(jvm: JvmPtr) -> Result<AttachGuard> {
attached_or(jvm, || {
Ok(AttachGuard {
jvm,
env: unsafe { jvm.attach_thread()? },
permanent: true,
})
})
}
pub unsafe fn attach<'jvm>(jvm: JvmPtr) -> Result<AttachGuard> {
attached_or(jvm, || {
Ok(AttachGuard {
jvm,
env: unsafe { jvm.attach_thread()? },
permanent: false,
})
})
}
pub struct AttachGuard {
jvm: JvmPtr,
env: EnvPtr<'static>, permanent: bool,
}
impl Drop for AttachGuard {
fn drop(&mut self) {
if self.permanent {
STATE.with(|state| {
let old_state = state.replace(State::AttachedPermanently(self.env));
debug_assert!(matches!(old_state, State::InUse))
});
} else {
match unsafe { self.jvm.detach_thread() } {
Ok(()) => STATE.with(|state| state.set(State::Detached)),
Err(err) => tracing::warn!(?err, "couldn't detach thread from JVM"),
}
}
}
}
impl AttachGuard {
pub fn env(&mut self) -> EnvPtr<'_> {
self.env
}
}