Skip to content

Commit 50d0221

Browse files
authored
[0200] 修复插件中连续点击中断导致 crash 的问题 (#3362)
1 parent 442f4d6 commit 50d0221

3 files changed

Lines changed: 107 additions & 30 deletions

File tree

devel/0200.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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` 发起的插件中断流程,不会波及其他功能。

src/Plugins/Qt/qt_pipe_link.cpp

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#if defined(OS_MINGW) || defined(OS_WIN)
2727
#else
2828
#include <sys/wait.h>
29+
#include <unistd.h>
2930
#endif
3031
#include <errno.h>
3132

@@ -142,32 +143,7 @@ qt_pipe_link_rep::is_readable (int channel) {
142143

143144
void
144145
qt_pipe_link_rep::interrupt () {
145-
if (!alive) return;
146-
#if defined(OS_MINGW) || defined(OS_WIN)
147-
// Not implemented
148-
qt_error << "SIGINT not implemented on Windows\n";
149-
#else
150-
#if QT_VERSION < 0x060000
151-
Q_PID pid= PipeLink.pid ();
152-
153-
// REMARK: previously there were here below a call to ::killpg which does not
154-
// seems to work on MacOS I (mgubi) replaced it with ::kill which does the
155-
// job. But I do not undestand the difference.
156-
157-
int ret= ::kill (pid, SIGINT);
158-
if (ret == -1) {
159-
qt_error << "Interrupt not successful, pid: " << pid
160-
<< " return code: " << errno << "\n";
161-
}
162-
#else
163-
uint64_t pid= PipeLink.processId ();
164-
int ret= ::kill (pid, SIGINT);
165-
if (ret == -1) {
166-
qt_error << "Interrupt not successful, pid: " << pid
167-
<< " return code: " << errno << "\n";
168-
}
169-
#endif
170-
#endif
146+
stop ();
171147
}
172148

173149
void

src/System/Link/pipe_link.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,7 @@ pipe_link_rep::listen (int msecs) {
313313

314314
void
315315
pipe_link_rep::interrupt () {
316-
#if !defined(OS_MINGW) && !defined(OS_WIN)
317-
if (!alive) return;
318-
killpg (pid, SIGINT);
319-
#endif
316+
stop ();
320317
}
321318

322319
void

0 commit comments

Comments
 (0)