JNI中创建ROS NodeHandle致程序无法响应SIGINT正常退出
根本原因
这个问题和CppBridge实例销毁阻塞没有关系,核心是roscpp(ROS C++客户端库)的默认行为和JVM的信号处理逻辑冲突:
- 调用
ros::init时如果不传入ros::init_options::NoSigintHandler选项,roscpp会主动注册SIGINT信号处理函数,覆盖JVM原本的SIGINT响应逻辑——JVM收到SIGINT本来会触发Spring Boot的正常关闭流程、执行Bean销毁、等待所有非守护线程退出后终止进程,被roscpp覆盖后,SIGINT只会触发roscpp内部的关闭标志位置位,不会通知JVM启动关闭流程。 - 创建
ros::NodeHandle时,roscpp会启动全局后台线程(包括XML-RPC通信线程、回调调度线程等),这些线程会一直持有进程运行资源,因为没有触发JVM关闭流程、也没有主动调用ros::shutdown()回收这些线程,进程就会一直挂住,只能通过SIGKILL强制终止。
修复步骤
- 第一步:修改ROS初始化逻辑,禁止roscpp接管SIGINT信号
修改CppBridge.cpp里的ros::init调用,添加NoSigintHandler选项,把信号处理权交还给JVM:
ros::init(argc, nullptr, "ifr_mimosa_bridge", ros::init_options::NoSigintHandler);
- 第二步:添加JNI层的ROS资源释放接口
首先在CppBridge.java中新增native的shutdown方法:
public class CppBridge { static { System.load(new File("backend/src/main/cpp/libcppbridge.so").getAbsolutePath()); } public native void start(); public native void shutdown(); }
修改CppBridge.cpp,将NodeHandle从start方法的栈变量改为全局持有,同时实现shutdown方法主动回收ROS资源:
#include <ros/ros.h> static ros::NodeHandle* g_privateNh = nullptr; JNIEXPORT void JNICALL Java_org_ifremer_rospipe_CppBridge_start(JNIEnv* env, jobject obj) { int argc = 0; ros::init(argc, nullptr, "ifr_mimosa_bridge", ros::init_options::NoSigintHandler); g_privateNh = new ros::NodeHandle("~"); } JNIEXPORT void JNICALL Java_org_ifremer_rospipe_CppBridge_shutdown(JNIEnv* env, jobject obj) { if (ros::ok()) { ros::shutdown(); // 阻塞等待所有ROS后台线程完全退出 ros::waitForShutdown(); } if (g_privateNh) { delete g_privateNh; g_privateNh = nullptr; } }
- 第三步:绑定Spring Boot生命周期,在应用关闭时主动释放ROS资源
修改RosPipe.java,添加@PreDestroy注解的销毁方法,在Spring容器关闭时调用ROS的shutdown逻辑:
@Component public class RosPipe { private final CppBridge mCppBridge = new CppBridge(); @PostConstruct() public void onStart() { mCppBridge.start(); } @PreDestroy public void onStop() { mCppBridge.shutdown(); } }
额外注意
如果后续需要处理ROS订阅/服务回调,不要直接在start方法中调用ros::spin()阻塞Spring启动线程,建议单独开一个C++后台线程执行spin逻辑,在shutdown流程中ros::shutdown()会自动终止该线程。
内容的提问来源于stack exchange,提问作者Tim Autin
相关产品推荐
相关产品推荐

