Skip to content

Commit bcb18af

Browse files
committed
Do not permanently attach JVM threads
We are a library and may be used in any kind of threading context, so leave this performance choice up to the caller. Additionally, optimize env access patterns to be more clear how many times we try attaching.
1 parent 042330d commit bcb18af

File tree

1 file changed

+44
-30
lines changed

1 file changed

+44
-30
lines changed

rustls-platform-verifier/src/android.rs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
2222
use jni::errors::Error as JNIError;
2323
use jni::objects::{GlobalRef, JClass, JObject, JValue};
24-
use jni::{JNIEnv, JavaVM};
24+
use jni::{AttachGuard, JNIEnv, JavaVM};
2525
use once_cell::sync::OnceCell;
2626

2727
static GLOBAL: OnceCell<Global> = OnceCell::new();
@@ -52,15 +52,15 @@ enum Global {
5252
}
5353

5454
impl Global {
55-
fn env(&self) -> Result<JNIEnv<'_>, Error> {
55+
fn env(&self) -> Result<AttachGuard<'_>, Error> {
5656
let vm = match self {
5757
Global::Internal { java_vm, .. } => java_vm,
5858
Global::External(global) => global.java_vm(),
5959
};
60-
Ok(vm.attach_current_thread_permanently()?)
60+
Ok(vm.attach_current_thread()?)
6161
}
6262

63-
fn context(&self) -> Result<Context<'_>, Error> {
63+
fn context(&self) -> Result<(GlobalContext, AttachGuard<'_>), Error> {
6464
let env = self.env()?;
6565

6666
let context = match self {
@@ -73,14 +73,21 @@ impl Global {
7373
Global::External(global) => global.class_loader(),
7474
};
7575

76-
Ok(Context {
77-
context: env.new_global_ref(context)?,
78-
loader: env.new_global_ref(loader)?,
76+
Ok((
77+
GlobalContext {
78+
context: env.new_global_ref(context)?,
79+
loader: env.new_global_ref(loader)?,
80+
},
7981
env,
80-
})
82+
))
8183
}
8284
}
8385

86+
struct GlobalContext {
87+
context: GlobalRef,
88+
loader: GlobalRef,
89+
}
90+
8491
fn global() -> &'static Global {
8592
GLOBAL
8693
.get()
@@ -178,29 +185,18 @@ impl From<JNIError> for Error {
178185
}
179186
}
180187

181-
pub(super) struct Context<'a> {
182-
env: JNIEnv<'a>,
188+
pub(super) struct LocalContext<'a, 'env> {
189+
env: &'a mut JNIEnv<'env>,
183190
context: GlobalRef,
184191
loader: GlobalRef,
185192
}
186193

187-
impl<'a> Context<'a> {
188-
/// Borrow a reference to the JNI Environment executing the Android application
189-
pub(super) fn env(&mut self) -> &mut JNIEnv<'a> {
190-
&mut self.env
191-
}
192-
193-
/// Borrow the `applicationContext` from the Android application
194-
/// <https://developer.android.com/reference/android/app/Application>
195-
pub(super) fn application_context(&self) -> &JObject<'a> {
196-
&self.context
197-
}
198-
194+
impl<'a, 'env> LocalContext<'a, 'env> {
199195
/// Load a class from the application class loader
200196
///
201197
/// This should be used instead of `JNIEnv::find_class` to ensure all classes
202198
/// in the application can be found.
203-
pub(super) fn load_class(&mut self, name: &str) -> Result<JClass<'a>, Error> {
199+
fn load_class(&mut self, name: &str) -> Result<JClass<'env>, Error> {
204200
let name = self.env.new_string(name)?;
205201
let class = self.env.call_method(
206202
&self.loader,
@@ -211,21 +207,39 @@ impl<'a> Context<'a> {
211207

212208
Ok(JObject::try_from(class)?.into())
213209
}
210+
211+
/// Borrow the `applicationContext` from the Android application
212+
/// <https://developer.android.com/reference/android/app/Application>
213+
pub(super) fn application_context(&self) -> &JObject<'_> {
214+
&self.context
215+
}
214216
}
215217

216218
/// Borrow the Android application context and execute the closure
217219
/// `with_context, ensuring locals are properly freed and exceptions
218220
/// are cleared.
219221
pub(super) fn with_context<F, T>(f: F) -> Result<T, Error>
220222
where
221-
F: FnOnce(&mut Context, &mut JNIEnv) -> Result<T, Error>,
223+
F: FnOnce(&mut LocalContext, &mut JNIEnv) -> Result<T, Error>,
222224
{
223-
let mut context = global().context()?;
224-
let mut binding = global().context()?;
225-
let env = binding.env();
225+
let (global_context, mut binding_env) = global().context()?;
226+
// SAFETY: Any local references created with this env are always destroyed prior to the parent
227+
// frame exiting because we force it to be dropped before the new frame exits and don't allow
228+
// the closure to access the env directly. We don't use it anywhere outside that sub-scope either.
229+
//
230+
// Rust's borrowing rules enforce that any reference that came from this env must be dropped before it is too.
231+
let ctx_env = unsafe { binding_env.unsafe_clone() };
226232

227233
// 16 is the default capacity in the JVM, we can make this configurable if necessary
228-
env.with_local_frame(16, |env| f(&mut context, env))
234+
binding_env.with_local_frame(16, |env| {
235+
let mut ctx_env = ctx_env;
236+
let mut context = LocalContext {
237+
env: &mut ctx_env,
238+
context: global_context.context,
239+
loader: global_context.loader,
240+
};
241+
f(&mut context, env)
242+
})
229243
}
230244

231245
/// Loads and caches a class on first use
@@ -244,11 +258,11 @@ impl CachedClass {
244258
}
245259

246260
/// Gets the cached class reference, loaded on first use
247-
pub(super) fn get<'a: 'b, 'b>(&'a self, cx: &mut Context<'b>) -> Result<&'a JClass<'b>, Error> {
261+
pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'_>, Error> {
248262
let class = self.class.get_or_try_init(|| -> Result<_, Error> {
249263
let class = cx.load_class(self.name)?;
250264

251-
Ok(cx.env().new_global_ref(class)?)
265+
Ok(cx.env.new_global_ref(class)?)
252266
})?;
253267

254268
Ok(class.as_obj().into())

0 commit comments

Comments
 (0)