|
| 1 | +# [0200] 修复中断执行时主进程意外退出的问题 |
| 2 | + |
| 3 | +## 相关文档 |
| 4 | +- [dddd.md](dddd.md) - 任务文档模板 |
| 5 | + |
| 6 | +## 任务相关的代码文件 |
| 7 | +- `src/System/Link/pipe_link.cpp` |
| 8 | +- `src/Plugins/Qt/qt_pipe_link.cpp` |
| 9 | + |
| 10 | +## 如何测试 |
| 11 | + |
| 12 | +### 确定性测试(单元测试) |
| 13 | +暂无单元测试。 |
| 14 | + |
| 15 | +### 非确定性测试(文档验证) |
| 16 | +1. 启动 Mogan,打开一个 LLM 插件会话 |
| 17 | +2. 输入一个需要较长时间执行的命令(如多行代码生成) |
| 18 | +3. 在命令执行期间点击 "Interrupt execution"(停止图标) |
| 19 | +4. 确认: |
| 20 | + - 插件子进程被终止 |
| 21 | + - 主进程不会意外退出 |
| 22 | + - 可以正常继续使用 Mogan |
| 23 | +5. 重复多次中断操作,确认稳定性 |
| 24 | + |
| 25 | +## 如何提交 |
| 26 | + |
| 27 | +提交前执行以下最少步骤: |
| 28 | + |
| 29 | +```bash |
| 30 | +xmake b stem |
| 31 | +``` |
| 32 | + |
| 33 | +确认编译通过后提交并创建 PR: |
| 34 | + |
| 35 | +```bash |
| 36 | +git add -A |
| 37 | +git commit -m "[0200] 修复中断执行时主进程意外退出的问题" |
| 38 | +git push origin hongwei/200_27/fix_interrupt |
| 39 | +git pr create |
| 40 | +``` |
| 41 | + |
| 42 | +## What |
| 43 | + |
| 44 | +修复在插件会话中点击 "Interrupt execution" 时,主进程意外退出的问题。 |
| 45 | + |
| 46 | +修改内容: |
| 47 | +1. `qt_pipe_link.cpp`:`interrupt()` 改为调用 `stop()`,不再发送 `SIGINT` |
| 48 | +2. `pipe_link.cpp`:`interrupt()` 改为调用 `stop()`,不再发送 `SIGINT` |
| 49 | + |
| 50 | +## Why |
| 51 | + |
| 52 | +### 根因分析 |
| 53 | + |
| 54 | +日志显示第二次中断时 `target_pid=0`: |
| 55 | + |
| 56 | +``` |
| 57 | +[DEBUG qt_pipe_link_rep::interrupt] target_pid=42922 |
| 58 | +... |
| 59 | +[DEBUG qt_pipe_link_rep::interrupt] target_pid=0 |
| 60 | +``` |
| 61 | + |
| 62 | +问题出在这个调用链: |
| 63 | + |
| 64 | +1. 第一次 `interrupt()` 调用 `::kill(pid, SIGINT)` 发信号给子进程 `42922` |
| 65 | +2. 子进程收到 `SIGINT` 后退出 |
| 66 | +3. 但 Qt 的 `QProcess` 在进程结束后 `pid()` / `processId()` 返回了 `0` |
| 67 | +4. `alive` 标志未被更新(`connection_rep::interrupt()` 只改 `status`,不改 `alive`) |
| 68 | +5. 第二次 `interrupt()` 调用时,`::kill(0, SIGINT)` 把信号发给了**当前进程组的所有进程** |
| 69 | +6. 主进程自己也收到了 `SIGINT`,触发 `clean_exit_on_signal` 退出 |
| 70 | + |
| 71 | +### POSIX 语义 |
| 72 | + |
| 73 | +`::kill(0, SIGINT)` 的语义是:把信号发送给**发送者所在进程组的所有进程**。当 `target_pid=0` 时,主进程会自杀。 |
| 74 | + |
| 75 | +## How |
| 76 | + |
| 77 | +将两个 `interrupt()` 的实现从发送 `SIGINT` 改为调用 `stop()`: |
| 78 | + |
| 79 | +```cpp |
| 80 | +// src/Plugins/Qt/qt_pipe_link.cpp |
| 81 | +void qt_pipe_link_rep::interrupt () { |
| 82 | + stop (); |
| 83 | +} |
| 84 | + |
| 85 | +// src/System/Link/pipe_link.cpp |
| 86 | +void pipe_link_rep::interrupt () { |
| 87 | + stop (); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +`stop()` 的行为: |
| 92 | +- `qt_pipe_link_rep::stop()`:`PipeLink.killProcess(0)` 终止 Qt 子进程,`alive = false` |
| 93 | +- `pipe_link_rep::stop()`:发送 `SIGTERM`/`SIGKILL` 终止子进程,关闭管道,`alive = false` |
| 94 | + |
| 95 | +这样避免了 `kill(pid=0)` 导致的主进程自杀风险,同时正确更新了 `alive` 状态。 |
| 96 | + |
| 97 | +## 影响范围确认 |
| 98 | + |
| 99 | +经全项目检索,`interrupt()` 的调用方**仅有** `src/System/Link/connection.cpp` 内部的两处: |
| 100 | + |
| 101 | +1. `connection_rep::interrupt()` 中调用 `ln->interrupt()` |
| 102 | +2. `connection_interrupt()` 中调用 `con->interrupt()` |
| 103 | + |
| 104 | +项目中不存在任何其他直接或间接调用 `tm_link_rep::interrupt()`、`qt_pipe_link_rep::interrupt()` 或 `pipe_link_rep::interrupt()` 的代码。因此本次修改只影响通过 `connection-interrupt` 发起的插件中断流程,不会波及其他功能。 |
0 commit comments