diff --git a/jni/exports.rs b/jni/exports.rs index 4dcfb56..3624d9e 100644 --- a/jni/exports.rs +++ b/jni/exports.rs @@ -1,6 +1,6 @@ use crate::jni_bindings::callbacks::register_callbacks; use crate::jni_bindings::env::JavaCallback; -use crate::jni_bindings::server::{destroy_handle, handle_ref, into_handle_ptr, ServerHandle}; +use crate::jni_bindings::server::{handle_ref, into_handle_ptr, take_handle, ServerHandle}; use crate::jni_bindings::types::java_to_dht_options_or_default; use jni::JNIEnv; use jni::objects::{JClass, JObject}; @@ -102,14 +102,12 @@ fn jni_shutdown_and_release(env: &mut JNIEnv, handle: jlong) { return; } unsafe { - match handle_ref(handle) { - Some(h) => h.stop(), + match take_handle(handle) { + Some(h) => h.shutdown_and_destroy_in_background(), None => { let _ = env.throw_new("java/lang/IllegalArgumentException", "无效的服务器句柄"); - return; } } - destroy_handle(handle); } }); } diff --git a/jni/server.rs b/jni/server.rs index c062056..45dabf8 100644 --- a/jni/server.rs +++ b/jni/server.rs @@ -34,11 +34,31 @@ impl ServerHandle { Ok(()) } - /// 发送关闭信号(非阻塞)。 + /// 发送关闭信号(非阻塞)。仅供单独使用,通常应调用 `shutdown_and_destroy_in_background`。 + #[allow(dead_code)] pub fn stop(&self) { self.server.shutdown(); } + /// 在专用后台线程中 drop 整个 handle(含 Runtime), + /// 避免 Runtime::drop 阻塞 JNI 调用线程。 + /// 先发关闭信号,然后把 handle 所有权移入后台线程; + /// 后台线程等待 tokio runtime 中所有任务退出后统一释放资源。 + pub fn shutdown_and_destroy_in_background(self) { + self.server.shutdown(); + if let Err(e) = std::thread::Builder::new() + .name("dht-jni-shutdown".to_owned()) + .spawn(move || { + // drop(self) 在此发生:Runtime::drop 阻塞等待所有 tokio 任务退出 + // 但此时已在独立线程,不会卡 JNI 调用线程 + drop(self); + }) + { + // 线程创建失败(极罕见),fallback 在当前线程同步释放,保底不泄漏 + log::error!("dht-jni-shutdown 线程创建失败,在当前线程同步释放: {e}"); + } + } + /// 返回节点池大小。 pub fn node_pool_size(&self) -> usize { self.server.get_node_pool_size() @@ -65,10 +85,25 @@ pub unsafe fn handle_ref<'a>(ptr: i64) -> Option<&'a ServerHandle> { Some(unsafe { &*(ptr as *const ServerHandle) }) } +/// 消费句柄:从裸指针重建 Box 并返回 `ServerHandle` 所有权。 +/// 调用后 Java 侧不得再使用该句柄。 +/// +/// # Safety +/// 只能调用一次;ptr 必须是由 `into_handle_ptr` 生成的合法指针。 +pub unsafe fn take_handle(ptr: i64) -> Option { + if ptr == 0 { + return None; + } + Some(*unsafe { Box::from_raw(ptr as *mut ServerHandle) }) +} + /// 消费句柄:从裸指针重建 Box 并 drop,释放所有资源(包括 runtime)。 +/// 注意:会阻塞当前线程直到 Runtime 中所有任务退出。 +/// 通常应优先使用 `take_handle` + `shutdown_and_destroy_in_background`。 /// /// # Safety /// 只能调用一次,调用后 Java 侧不得再使用该句柄。 +#[allow(dead_code)] pub unsafe fn destroy_handle(ptr: i64) { if ptr != 0 { drop(unsafe { Box::from_raw(ptr as *mut ServerHandle) });