Skip to content

Commit 1bdf7b8

Browse files
committed
fix: make fatal shutdown diagnostics visible
1 parent 644d482 commit 1bdf7b8

9 files changed

Lines changed: 136 additions & 33 deletions

File tree

docs/MDTODO/details/pikapython-syntax-compatibility/R14.16_Task_Report.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@
2626
- 定向 GTest:递归正常调用、递归错误传播、单段/嵌套 host 对象路径共 3/3 通过。
2727
- 定向 Valgrind:11,688 allocs / 11,688 frees,退出 0 bytes,0 leaks,0 errors。
2828
- Docker 默认配置(`PIKA_CONFIG_ENABLE=ON`、4096 B VM stack)隔离网络及既定不稳定 REPL 项后,454/454 通过。
29-
- 曾误复用 `PIKA_CONFIG_ENABLE=OFF` 的 512 B stack 构建,测试在请求 528 B 时进入既有 overflow handler;改用全新默认配置构建后全量通过,该错误口径未计入结论。
29+
- 曾误复用 `PIKA_CONFIG_ENABLE=OFF` 的 512 B stack 构建,测试请求 528 B 时清晰报告 `OverflowError`、请求大小和当前容量,随后进入永久停止状态。
30+
- 该停止状态符合资源受限微控制器的致命故障设计:真实 VM 栈耗尽后必须停止,不能恢复执行;它不是本轮性能回归,也不是需要改成可捕获返回的普通错误。
31+
32+
## VM 栈错误边界
33+
34+
- 参数槽位超限、非法语法和其他可在写入前识别的边界必须返回明确错误,禁止越界写或静默死机。
35+
- VM 数据栈已经真实耗尽时属于致命故障:必须先清晰报告,再永久停机或进入平台 panic。
36+
- 后续小栈测试应使用受控子进程或 watchdog 验证“报告完整且停止执行”,不能以进程继续运行或正常退出作为验收条件。
3037

3138
## 在线基准
3239

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# R14.17 任务报告
2+
3+
## 结论
4+
5+
真实 VM 栈耗尽继续保持 MCU 致命故障语义:先输出明确诊断,再永久停止,禁止恢复执行。本轮修复了缓冲输出模式下停机报告可能未刷新而静默的问题,并将其他明确不可恢复路径收敛到同一停机顺序,未改变 VM、parser 或对象系统架构。
6+
7+
## 修复
8+
9+
- `pika_platform_fflush()` 改为始终执行显式刷新;`PIKA_UNBUFFERED_ENABLE` 仅控制 `pika_putchar()` 是否逐字符自动刷新,不再屏蔽调用方主动刷新。
10+
- 新增 `pika_platform_fatal_handle()`:先显式刷新,再进入可由平台覆盖的 panic,若平台 panic 意外返回则以死循环兜底,保证致命故障不恢复执行;helper 位于 `PIKA_PLATFORM_NO_WEAK` 条件外,自定义平台仍只需实现既有 flush/panic 接口。
11+
- VM 栈耗尽报告包含请求字节数、配置容量、`PIKA_STACK_BUFF_SIZE` 和“execution stopped”,随后进入统一 fatal-stop。
12+
- 堆耗尽、方法声明行缓冲区耗尽、`pika_sprintf` 缓冲区耗尽、禁用 event 功能后误用 event API 的直接死循环改为明确 FatalError 后统一停机。
13+
- `methodArg_getTypeList()` 的容量判断由 `>` 修正为 `>=`,覆盖字符串长度恰好等于缓冲区容量但仍缺少结尾 NUL 的一字节越界边界。
14+
15+
## 测试
16+
17+
- 新增 Linux `fork + pipe + poll` watchdog 单元测试:子进程将 stdout 设为全缓冲后触发 VM 栈耗尽;父进程验证 FatalError、80/64 字节容量信息和停止说明完整出现,并验证子进程未返回,最后以 `SIGKILL` 受控回收。
18+
- 默认 Linux 配置定向 stack 测试 2/2 通过。
19+
- 临时设置 `PIKA_UNBUFFERED_ENABLE=0` 后,反汇编确认 `pika_putchar()` 不含逐字符 flush;同一 fatal watchdog 仍通过,直接覆盖原静默条件。配置随后恢复,未进入提交。
20+
- Docker `pikadev``PIKA_CONFIG_ENABLE=ON`、隔离网络/LVGL及既定不稳定 REPL 项后,455/455 通过。
21+
- Valgrind 需要把 Docker 异常的 `nofile=1073741816` 收敛为 65536;普通 stack 与 fatal watchdog 均通过,0 bytes in use、0 leaks、0 errors。
22+
- `PIKA_PLATFORM_NO_WEAK=1` 条件下单独编译 `PikaPlatform.c` 通过,未增加自定义平台必须实现的新符号。
23+
24+
## 资源
25+
26+
`origin/master``644d482cd` 为基线,在 Docker 内使用相同生成 API 集、Release、`PIKA_CONFIG_ENABLE=ON` 比较 core 静态库:
27+
28+
- text:1,041,589 -> 1,041,401 B,减少 188 B;
29+
- data:126,480 -> 126,480 B;
30+
- bss:24,342 -> 24,342 B。
31+
32+
统一 fatal-stop helper 和压缩后的具体诊断抵消了错误可见性新增代码,RAM/Flash 总体未膨胀。

docs/MDTODO/pikapython-syntax-compatibility.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,7 @@ P0 修复空 import 与 from-import 缺名称在 Suger_import 路径的解析崩
358358
### R14.16 [completed]
359359

360360
在上一轮递归调用查找优化基础上重新采样 runtime/VM 热点,选择至少一个由 benchmark 证明且不改变大架构的性能债务完成同配置 A/B;同步探测相关 runtime 错误边界并补单元测试,保持 RAM/Flash 总体不膨胀,CI 仅后台验收不阻塞本轮开发,完成任务后将详细报告写入[任务报告](./details/pikapython-syntax-compatibility/R14.16_Task_Report.md)
361+
362+
### R14.17 [completed]
363+
364+
按 MCU 致命故障设计改进 VM 栈真实耗尽时的停机前错误可见性:保持清晰报告后永久停止,不恢复执行;盘点并修复停机信息缺失、不清晰或因缓冲未刷新而静默的路径,补受控子进程/watchdog 单元测试,并保持架构和资源总体不膨胀,完成任务后将详细报告写入[任务报告](./details/pikapython-syntax-compatibility/R14.17_Task_Report.md)

port/linux/test/pikaMain-test.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
#include "test_common.h"
2+
#include <poll.h>
3+
#include <signal.h>
4+
#include <sys/wait.h>
5+
#include <unistd.h>
26
TEST_START
37

48
extern "C" uint32_t stack_spSizeFree(Stack* stack);
9+
extern "C" void _stack_overflow_handler(Stack* stack, size_t stack_require);
510

611
TEST(stack, size_array_capacity) {
712
Stack stack = {0};
@@ -14,6 +19,63 @@ TEST(stack, size_array_capacity) {
1419
EXPECT_EQ(pikaMemNow(), 0);
1520
}
1621

22+
TEST(stack, fatal_overflow_reports_before_stopping) {
23+
int output_pipe[2] = {-1, -1};
24+
ASSERT_EQ(pipe(output_pipe), 0);
25+
fflush(stdout);
26+
27+
pid_t child = fork();
28+
ASSERT_NE(child, -1);
29+
if (child == 0) {
30+
close(output_pipe[0]);
31+
if (dup2(output_pipe[1], STDOUT_FILENO) == -1) {
32+
_exit(100);
33+
}
34+
close(output_pipe[1]);
35+
36+
static char output_buffer[1024];
37+
setvbuf(stdout, output_buffer, _IOFBF, sizeof(output_buffer));
38+
Stack stack = {0};
39+
stack.stack_totle_size = 64;
40+
_stack_overflow_handler(&stack, 80);
41+
_exit(101);
42+
}
43+
44+
close(output_pipe[1]);
45+
struct pollfd output = {output_pipe[0], POLLIN, 0};
46+
char report[1024] = {0};
47+
size_t report_size = 0;
48+
for (int retry = 0; retry < 100; retry++) {
49+
int poll_result = poll(&output, 1, 10);
50+
if (poll_result == 1) {
51+
ssize_t size =
52+
read(output_pipe[0], report + report_size,
53+
sizeof(report) - report_size - 1);
54+
if (size > 0) {
55+
report_size += (size_t)size;
56+
}
57+
}
58+
if (strstr(report, "execution stopped") != nullptr) {
59+
break;
60+
}
61+
}
62+
EXPECT_GT(report_size, 0);
63+
EXPECT_NE(strstr(report, "FatalError: VM stack exhausted"), nullptr);
64+
EXPECT_NE(strstr(report, "80/64 bytes, PIKA_STACK_BUFF_SIZE"), nullptr);
65+
EXPECT_NE(strstr(report, "execution stopped"), nullptr);
66+
67+
int status = 0;
68+
pid_t wait_result = waitpid(child, &status, WNOHANG);
69+
EXPECT_EQ(wait_result, 0);
70+
if (wait_result == 0) {
71+
EXPECT_EQ(kill(child, SIGKILL), 0);
72+
EXPECT_EQ(waitpid(child, &status, 0), child);
73+
EXPECT_TRUE(WIFSIGNALED(status));
74+
EXPECT_EQ(WTERMSIG(status), SIGKILL);
75+
}
76+
close(output_pipe[0]);
77+
}
78+
1779
TEST_RUN_LINES_EXCEPT_OUTPUT(
1880
issue,
1981
issue356_top_level_function_refs,

src/PikaObj.c

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,11 +1028,12 @@ char* methodArg_getTypeList(Arg* method_arg, char* buffs, size_t size) {
10281028
}
10291029
char* method_dec = methodArg_getDec(method_arg);
10301030
pika_assert(strGetSize(method_dec) <= size);
1031-
if (strGetSize(method_dec) > size) {
1031+
if (strGetSize(method_dec) >= size) {
10321032
pika_platform_printf(
1033-
"OverFlowError: please use bigger PIKA_LINE_BUFF_SIZE\r\n");
1034-
while (1) {
1035-
}
1033+
"FatalError: PIKA_LINE_BUFF_SIZE %u < required %u; stopped\r\n",
1034+
(unsigned int)size,
1035+
(unsigned int)(strGetSize(method_dec) + 1));
1036+
pika_platform_fatal_handle();
10361037
}
10371038
char* res = strCut(buffs, method_dec, '(', ')');
10381039
return res;
@@ -3247,9 +3248,9 @@ PIKA_RES _do_pika_eventListener_send(PikaEventListener* self,
32473248
PIKA_BOOL pickupWhenNoVM) {
32483249
pika_assert(NULL != self);
32493250
#if !PIKA_EVENT_ENABLE
3250-
pika_platform_printf("PIKA_EVENT_ENABLE is not enable");
3251-
while (1) {
3252-
};
3251+
pika_platform_printf("FatalError: event API requires PIKA_EVENT_ENABLE; "
3252+
"stopped\r\n");
3253+
pika_platform_fatal_handle();
32533254
#else
32543255
if (NULL != eventData && !_VM_is_first_lock()) {
32553256
#if PIKA_EVENT_THREAD_ENABLE
@@ -3346,9 +3347,9 @@ Arg* pika_eventListener_sendSignalAwaitResult(PikaEventListener* self,
33463347
* Await result from event.
33473348
* need implement `pika_platform_thread_delay()` to support thread switch */
33483349
#if !PIKA_EVENT_ENABLE
3349-
pika_platform_printf("PIKA_EVENT_ENABLE is not enable");
3350-
while (1) {
3351-
};
3350+
pika_platform_printf("FatalError: event API requires PIKA_EVENT_ENABLE; "
3351+
"stopped\r\n");
3352+
pika_platform_fatal_handle();
33523353
#else
33533354
extern volatile VMState g_PikaVMState;
33543355
int tail = g_PikaVMState.cq.tail;
@@ -4509,13 +4510,9 @@ int pika_sprintf(char* buff, char* fmt, ...) {
45094510
va_end(args);
45104511
if (res >= PIKA_SPRINTF_BUFF_SIZE) {
45114512
pika_platform_printf(
4512-
"OverflowError: sprintf buff size overflow, please use bigger "
4513-
"PIKA_SPRINTF_BUFF_SIZE\r\n");
4514-
pika_platform_printf("Info: buff size request: %d\r\n", res);
4515-
pika_platform_printf("Info: buff size now: %d\r\n",
4516-
PIKA_SPRINTF_BUFF_SIZE);
4517-
while (1)
4518-
;
4513+
"FatalError: PIKA_SPRINTF_BUFF_SIZE %d < required %d; stopped\r\n",
4514+
PIKA_SPRINTF_BUFF_SIZE, res + 1);
4515+
pika_platform_fatal_handle();
45194516
}
45204517
return res;
45214518
}

src/PikaPlatform.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,7 @@ PIKA_WEAK int64_t pika_platform_get_tick(void) {
243243
}
244244

245245
PIKA_WEAK int pika_platform_fflush(void* stream) {
246-
#if PIKA_UNBUFFERED_ENABLE
247246
return fflush(stream);
248-
#else
249-
return 0;
250-
#endif
251247
}
252248

253249
PIKA_WEAK int pika_platform_putchar(char ch) {
@@ -1207,6 +1203,7 @@ PIKA_WEAK void pika_platform_error_handle() {
12071203
}
12081204

12091205
PIKA_WEAK void pika_platform_panic_handle() {
1206+
pika_platform_fflush(stdout);
12101207
while (1) {
12111208
};
12121209
}
@@ -1228,3 +1225,10 @@ PIKA_WEAK void pika_thread_idle_hook(void) {
12281225
}
12291226

12301227
#endif
1228+
1229+
void pika_platform_fatal_handle() {
1230+
pika_platform_fflush(stdout);
1231+
pika_platform_panic_handle();
1232+
while (1) {
1233+
};
1234+
}

src/PikaPlatform.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ void pika_platform_error_handle(void);
235235

236236
/* panic */
237237
void pika_platform_panic_handle(void);
238+
void pika_platform_fatal_handle(void);
238239

239240
int64_t pika_platform_get_tick(void);
240241

src/dataMemory.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ void* pikaMalloc(uint32_t size) {
5252
pika_platform_enable_irq_handle();
5353
if (NULL == mem) {
5454
pika_platform_printf(
55-
"Error: No heap space! Please reset the device.\r\n");
56-
while (1) {
57-
}
55+
"FatalError: heap exhausted (%u-byte allocation); stopped\r\n",
56+
(unsigned int)size);
57+
pika_platform_fatal_handle();
5858
}
5959
return mem;
6060
}

src/dataStack.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,10 @@
3030

3131
void _stack_overflow_handler(Stack* stack, size_t stack_require) {
3232
pika_platform_printf(
33-
"OverflowError: pika VM stack overflow, please use bigger "
34-
"PIKA_STACK_BUFF_SIZE\r\n");
35-
pika_platform_printf("Info: stack size request: %d\r\n",
36-
(int)stack_require);
37-
pika_platform_printf("Info: stack size now: %d\r\n",
38-
(int)stack->stack_totle_size);
39-
while (1) {
40-
}
33+
"FatalError: VM stack exhausted (%d/%d bytes, "
34+
"PIKA_STACK_BUFF_SIZE); execution stopped\r\n",
35+
(int)stack_require, (int)stack->stack_totle_size);
36+
pika_platform_fatal_handle();
4137
}
4238

4339
uint8_t* stack_popPyload(Stack* stack, int32_t size);

0 commit comments

Comments
 (0)