فهرست منبع

优化: 修复停机卡死/内存浪费/通道删除崩溃风险

1. 停机卡死(严重): 停止推理线程未notify条件变量, worker永久阻塞导致join挂起
   - 新增stopChannelInfer统一封装(设标志+notify_all+join)
   - 退出/删除通道/替换通道全部改用该封装
   - 实测: 重启从90秒降到3.8秒
2. 通道删除/修改崩溃风险(严重): 只停capture未停推理线程, use-after-free
   - 删除/修改通道前先stopChannelInfer
3. 内存浪费: 共享detector/poseDetector加载后从未使用(每路已有独立context)
   - 改为加载验证后立即释放, 内存410MB->359MB
4. 每路模型加载失败增加告警日志
5. 手机端报警列表仅在第一页时自动刷新, 避免打断翻页
dev 23 ساعت پیش
والد
کامیت
4ce78ce37c
2فایلهای تغییر یافته به همراه37 افزوده شده و 23 حذف شده
  1. 34 21
      src/main.cpp
  2. 3 2
      web/mobile.html

+ 34 - 21
src/main.cpp

@@ -499,14 +499,16 @@ int main(int argc, char* argv[]) {
     // NPU 核心分配: 检测绑 Core0, 姿态绑 Core1, 两模型并行推理
     int det_core = cfg.value("det_core_mask", 1);   // 默认 Core0
     int pose_core = cfg.value("pose_core_mask", 2); // 默认 Core1
-    YoloDetector detector;
-    if (target_enabled && !detector.load(model_path, input_size, conf_thresh, nms_thresh, det_core)) {
-        cerr << "RKNN模型加载失败!" << endl;
-        return 1;
+    // 目标模型: 仅做一次加载验证(验证后立即释放, 实际推理用每路独立context)
+    if (target_enabled) {
+        YoloDetector probe;
+        if (!probe.load(model_path, input_size, conf_thresh, nms_thresh, det_core)) {
+            cerr << "RKNN模型加载失败!" << endl;
+            return 1;
+        }
+        cout << "[RKNN] 检测模型验证通过, 核心掩码: " << det_core << endl;
     }
-    cout << "[RKNN] 检测核心掩码: " << det_core << endl;
 
-    PoseDetector poseDetector;
     FallAnalyzer fallAnalyzer;
     fallAnalyzer.setThreshold(fall_thresh);
     bool pose_hand_raise = cfg.value("pose_hand_raise", true);
@@ -553,10 +555,12 @@ int main(int argc, char* argv[]) {
          << " 屏蔽区=" << pose_exclude_zones.size() << "路" << endl;
     bool pose_ok = false;
     if (pose_enabled) {
-        if (poseDetector.load(pose_model_path, pose_input_size,
-                              pose_conf_thresh, pose_nms_thresh, pose_core)) {
+        // 姿态模型: 加载验证后立即释放(实际推理用每路独立context)
+        PoseDetector probe;
+        if (probe.load(pose_model_path, pose_input_size,
+                       pose_conf_thresh, pose_nms_thresh, pose_core)) {
             pose_ok = true;
-            cout << "[Pose] 姿态模型加载成功, 跌倒检测已就绪, 核心掩码: " << pose_core << endl;
+            cout << "[Pose] 姿态模型验证通过, 核心掩码: " << pose_core << endl;
         } else {
             cerr << "[Pose] 姿态模型加载失败: " << pose_model_path
                  << " (继续使用纯围栏检测)" << endl;
@@ -671,6 +675,15 @@ int main(int argc, char* argv[]) {
         }
     });
 
+    // 停止通道的推理线程(删除/替换通道前必须调用, 避免use-after-free)
+    auto stopChannelInfer = [](const shared_ptr<ChannelState>& ch) {
+        if (!ch) return;
+        ch->infer_running = false;
+        ch->infer_pending = false;
+        ch->infer_cv.notify_all();
+        if (ch->infer_thread.joinable()) ch->infer_thread.join();
+    };
+
     auto makeChannel = [&](int id, const string& name, const string& url) {
         auto ch = make_shared<ChannelState>();
         ch->id = id;
@@ -684,10 +697,14 @@ int main(int argc, char* argv[]) {
         if (target_enabled) {
             ch->det_loaded = ch->det_model.load(model_path, input_size,
                                                 conf_thresh, nms_thresh, core);
+            if (!ch->det_loaded)
+                cerr << "[CH" << id << "] 检测模型加载失败(该路不做目标识别)" << endl;
         }
         if (pose_enabled) {
             ch->pose_loaded = ch->pose_model.load(pose_model_path, pose_input_size,
                                                   pose_conf_thresh, pose_nms_thresh, core);
+            if (!ch->pose_loaded)
+                cerr << "[CH" << id << "] 姿态模型加载失败(该路不做姿态识别)" << endl;
         }
         // 启动每路独立推理线程
         if (ch->det_loaded || ch->pose_loaded) {
@@ -1330,7 +1347,10 @@ int main(int argc, char* argv[]) {
                 if (!old) channels.push_back(nu);
             }
             registerChannelProviders(nu);
-            if (old) old->capture->stop();   // 锁外停旧流(join可能耗时)
+            if (old) {
+                stopChannelInfer(old);   // 先停旧通道推理线程
+                old->capture->stop();    // 锁外停旧流(join可能耗时)
+            }
             persistRtspList();
             cout << "[Channel] " << (old ? "修改" : "添加")
                  << " CH" << id << ": " << url << endl;
@@ -1359,7 +1379,8 @@ int main(int argc, char* argv[]) {
             if (!victim)
                 return R"({"ok":false,"error":"通道不存在"})";
             web.removeFrameProvider(id);
-            victim->capture->stop();   // 锁外停止并回收解码线程
+            stopChannelInfer(victim);   // 先停推理线程(避免访问已释放的ChannelState)
+            victim->capture->stop();    // 锁外停止并回收解码线程
             persistRtspList();
             cout << "[Channel] 删除 CH" << id << endl;
             return R"({"ok":true})";
@@ -2482,22 +2503,14 @@ int main(int argc, char* argv[]) {
         lock_guard<mutex> lk(channels_mtx);
         for (auto& ch : channels) {
             ch->capture->stop();
-            if (ch->infer_running.load()) {
-                ch->infer_running = false;
-                ch->infer_pending = false;
-                if (ch->infer_thread.joinable()) ch->infer_thread.join();
-            }
+            stopChannelInfer(ch);   // 内含 notify_all, 避免worker永久阻塞在wait
         }
     }
     {
         lock_guard<mutex> lk(bucket_channels_mtx);
         for (auto& ch : bucket_channels) {
             ch->capture->stop();
-            if (ch->infer_running.load()) {
-                ch->infer_running = false;
-                ch->infer_pending = false;
-                if (ch->infer_thread.joinable()) ch->infer_thread.join();
-            }
+            stopChannelInfer(ch);
         }
     }
     relay.stop();   // 先停继电器线程, 避免HTTP处理线程阻塞在transact上拖住web.stop()

+ 3 - 2
web/mobile.html

@@ -375,9 +375,10 @@ document.getElementById('alarmPage').addEventListener('scroll', () => {
   }
 });
 
-// 每10秒刷新报警列表(回到第一页)
+// 每10秒刷新报警(仅当停在第一页时, 避免打断用户翻页)
 setInterval(() => {
-  if (!document.getElementById('alarmPage').classList.contains('hidden')) loadAlarms(true);
+  if (document.getElementById('alarmPage').classList.contains('hidden')) return;
+  if (alarmPage <= 1 && !alarmLoading) loadAlarms(true);
 }, 10000);
 </script>
 </body>