fix(jni): stopServer 改为后台线程释放 Runtime,避免阻塞 JNI 调用线程

- 新增 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
This commit is contained in:
桥下红药
2026-03-14 14:42:23 +08:00
parent e4740f5fff
commit bcc166c53b
2 changed files with 39 additions and 6 deletions
+3 -5
View File
@@ -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);
}
});
}
+36 -1
View File
@@ -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<ServerHandle> {
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) });