From bcc166c53b896f8cd9409634c3e1a9cd34f9b501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=A5=E4=B8=8B=E7=BA=A2=E8=8D=AF?= Date: Sat, 14 Mar 2026 14:42:23 +0800 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfix(jni):=20stopServer=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=90=8E=E5=8F=B0=E7=BA=BF=E7=A8=8B=E9=87=8A=E6=94=BE?= =?UTF-8?q?=20Runtime=EF=BC=8C=E9=81=BF=E5=85=8D=E9=98=BB=E5=A1=9E=20JNI?= =?UTF-8?q?=20=E8=B0=83=E7=94=A8=E7=BA=BF=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 take_handle: 转移 ServerHandle 所有权,替代 handle_ref+destroy_handle 两步调用 - 新增 shutdown_and_destroy_in_background: 先发关闭信号,再将整个 ServerHandle(含 tokio Runtime)移入专用后台线程 drop,避免 Runtime::drop 阻塞调用 stopServer 的 Java 线程 - jni_shutdown_and_release 改用 take_handle + shutdown_and_destroy_in_background - 保留 stop/destroy_handle 供扩展,标注 #[allow(dead_code)] 并补充注释 Made-with: Cursor --- jni/exports.rs | 8 +++----- jni/server.rs | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) 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) });