fix(serial-assistant): daemon 重启自动重开口 + 自动重连 + 日志缺口标记(#75) #76

Merged
wandering1 merged 1 commits from improve-serial-assistant-8e959e85113d into master 2026-11-04 11:39:11 +08:00
Collaborator

This is by an automated agent, not a human.

问题摘要(Gitea issue #75)

serial-assistant 采集层的 daemon 一旦崩溃/被杀/读错丢口,旧行为是「新实例起得来但口不开、serial.log 静默追加无边界、wait-idle 计满还良性返回 no_data」——采集静默停摆,且读日志方无法区分「板子安静」与「采集器死」。用户批示「按照你的建议进行修复测试」,本 PR 按工单登记的 R1–R5 实施,不扩面。

改动对照 R1–R5

# 工单要求 实施(均在 .dsh/skills/serial-assistant/scripts/serial_tool.py)
R1 启动自动重开 meta.json 的 port 字段升格为「开口意图」现场(_save_meta 写 _open_identity(),缺口期不清空;显式 close//stop 清除)。run() 先读旧 meta 再覆写(旧实现覆写在前、现场即被抹成 null = 根因);重开在串口线程内执行(串口对象唯一持有者不变量),首次 = 出生 +1.5s。restart 特判:cmd_start(force) 在 POST /stop 前抓 /status 现场、新 daemon 就绪后 POST /open 开回来(stop 语义不变,结果进返回 JSON 的 reopen 键)
R2 失败自动重连 _port_error 后退避重试:1.5s 起步、倍增、30s 封顶,直到成功或显式 close;逐次失败原文进 daemon.log(dlog=stderr),不刷 serial.log
R3 缺口标记落盘 serial.log [SYS] 行四种:daemon started v… pid=… prev_pid=… auto-reopen pending …(启动边界)/ GAP-START(丢口与崩溃恢复两种,后者声明窗口起点下限)/ GAP-END reopened … after Ss, N attempt(s); bytes … unrecoverable(缺口止)/ GAP-RETRY(退避期仅首次 + 之后每分钟 ≤1 条,口径定死写入设计说明 §6.1)
R4 状态可见 status 新增 auto_reopen={enabled,pending,attempts,last_error,next_retry_in_ms};port 新增 desired(意图)与 ready(实际)并列,缺口期 name/baud 显示待重开参数;wait-idle 入口加无口门:口未开秒回 ok:false + auto-reopen 现场,不再计满返回良性 no_data
R5 版本/自检/文档 VERSION=1.2.0-skill;selftest 新增 T10–T15(重开意图判定含 close 后不重开/崩溃现场重开、退避倍增封顶、缺口行格式、无口门、auto_reopen 三态、restart 捕获),合计 45 项;SKILL.md(4713B)、使用指南(行为+新字段示例)、设计说明(§6.1 机理单一详述处 + §13 版本记录)同步

兼容性:JSON 只增字段、退出码形状不变(0/1/2/3)。run_boot_test.py 正常路径(open→clear→wait-idle)不受影响;其 wait-idle 撞无口时按采集错误 exit 2 归因而非「板子静默」——正是工单要的区分。

验证证据

  • 无头:selftest 45/45 PASS(.venv Python 3.14.7,exit 0);文件无 BOM、LF、无 U+FFFD。
  • COM3 台架实测(台架锁下,两把锁已按规程获取并释放):四场景 19/19 断言全过,transcript 已随汇报存档:
    • S1 崩溃恢复:open→taskkill /F→10s 后任意 CLI 自动拉起→port.ready=true、desired=true;serial.log 依次出现 daemon started … prev_pid=<旧pid> auto-reopen pending COM3@115200 + GAP-START after daemon restart … + GAP-END reopened COM3@115200 (gen=1) after 1.5s, 1 attempt(s);期间板上 [PROF] 流真实恢复。
    • S2 显式关闭:close→restart→不自动重开(ready=false、desired=false、auto_reopen.enabled=false、返回 JSON 无 reopen 键)。
    • S3 无口 wait-idle:rc=1、ok:false、0.1s 秒回(非 no_data),error 带 auto-reopen 现场。
    • S4 restart 带口正路径:restart 返回 reopen.requested.port=COM3 且 reopen.result.ok=true,重启后口就绪。
  • 提交门禁:pre-commit 实跑钩子全 Passed(含 gitlint、doc-links、skill-size;C 类钩子 no files to check 属预期跳过,本次无固件改动),未用 --no-verify。

遗留风险

  • 缺口标记 [SYS] 行为英文(serial.log 行内文本与既有 error/opened 行同口径,避免混编码);机理/口径全文在 docs/测试说明/测试工具/串口助手设计说明.md §6.1。
  • 退避封顶 30s 的持续重试在「口永久消失」场景会一直每分钟留一条 GAP-RETRY(预期行为,显式 close 即停)。
  • 台架实测时 COM3 挂着在跑的板子([PROF] 流),测试只做开关口/杀 daemon,未烧录未碰板。

Fixes #75

This is by an automated agent, not a human. ## 问题摘要(Gitea issue #75) serial-assistant 采集层的 daemon 一旦崩溃/被杀/读错丢口,旧行为是「新实例起得来但口不开、serial.log 静默追加无边界、wait-idle 计满还良性返回 no_data」——采集静默停摆,且读日志方无法区分「板子安静」与「采集器死」。用户批示「按照你的建议进行修复测试」,本 PR 按工单登记的 R1–R5 实施,不扩面。 ## 改动对照 R1–R5 | # | 工单要求 | 实施(均在 `.dsh/skills/serial-assistant/scripts/serial_tool.py`) | |---|---|---| | R1 | 启动自动重开 | `meta.json` 的 `port` 字段升格为「开口意图」现场(`_save_meta` 写 `_open_identity()`,缺口期不清空;显式 `close`/`/stop` 清除)。`run()` **先读旧 meta 再覆写**(旧实现覆写在前、现场即被抹成 null = 根因);重开在**串口线程内**执行(串口对象唯一持有者不变量),首次 = 出生 +1.5s。`restart` 特判:`cmd_start(force)` 在 `POST /stop` 前抓 `/status` 现场、新 daemon 就绪后 `POST /open` 开回来(`stop` 语义不变,结果进返回 JSON 的 `reopen` 键) | | R2 | 失败自动重连 | `_port_error` 后退避重试:1.5s 起步、倍增、30s 封顶,直到成功或显式 close;逐次失败原文进 daemon.log(`dlog`=stderr),不刷 serial.log | | R3 | 缺口标记落盘 | serial.log `[SYS]` 行四种:`daemon started v… pid=… prev_pid=… auto-reopen pending …`(启动边界)/ `GAP-START`(丢口与崩溃恢复两种,后者声明窗口起点下限)/ `GAP-END reopened … after Ss, N attempt(s); bytes … unrecoverable`(缺口止)/ `GAP-RETRY`(退避期仅首次 + 之后每分钟 ≤1 条,口径定死写入设计说明 §6.1) | | R4 | 状态可见 | `status` 新增 `auto_reopen={enabled,pending,attempts,last_error,next_retry_in_ms}`;`port` 新增 `desired`(意图)与 `ready`(实际)并列,缺口期 name/baud 显示待重开参数;`wait-idle` 入口加无口门:口未开**秒回** `ok:false` + auto-reopen 现场,不再计满返回良性 `no_data` | | R5 | 版本/自检/文档 | `VERSION=1.2.0-skill`;selftest 新增 T10–T15(重开意图判定含 close 后不重开/崩溃现场重开、退避倍增封顶、缺口行格式、无口门、auto_reopen 三态、restart 捕获),合计 45 项;SKILL.md(4713B)、使用指南(行为+新字段示例)、设计说明(§6.1 机理单一详述处 + §13 版本记录)同步 | 兼容性:JSON 只增字段、退出码形状不变(0/1/2/3)。`run_boot_test.py` 正常路径(open→clear→wait-idle)不受影响;其 wait-idle 撞无口时按**采集错误 exit 2** 归因而非「板子静默」——正是工单要的区分。 ## 验证证据 - **无头**:`selftest` **45/45 PASS**(`.venv` Python 3.14.7,exit 0);文件无 BOM、LF、无 U+FFFD。 - **COM3 台架实测(台架锁下,两把锁已按规程获取并释放)**:四场景 **19/19 断言全过**,transcript 已随汇报存档: - S1 崩溃恢复:`open`→`taskkill /F`→10s 后任意 CLI 自动拉起→`port.ready=true`、`desired=true`;serial.log 依次出现 `daemon started … prev_pid=<旧pid> auto-reopen pending COM3@115200` + `GAP-START after daemon restart …` + `GAP-END reopened COM3@115200 (gen=1) after 1.5s, 1 attempt(s)`;期间板上 `[PROF]` 流真实恢复。 - S2 显式关闭:`close`→`restart`→不自动重开(`ready=false`、`desired=false`、`auto_reopen.enabled=false`、返回 JSON 无 `reopen` 键)。 - S3 无口 wait-idle:rc=1、`ok:false`、0.1s 秒回(非 `no_data`),error 带 auto-reopen 现场。 - S4 restart 带口正路径:`restart` 返回 `reopen.requested.port=COM3` 且 `reopen.result.ok=true`,重启后口就绪。 - **提交门禁**:pre-commit 实跑钩子全 Passed(含 gitlint、doc-links、skill-size;C 类钩子 no files to check 属预期跳过,本次无固件改动),未用 `--no-verify`。 ## 遗留风险 - 缺口标记 `[SYS]` 行为英文(serial.log 行内文本与既有 error/opened 行同口径,避免混编码);机理/口径全文在 `docs/测试说明/测试工具/串口助手设计说明.md` §6.1。 - 退避封顶 30s 的持续重试在「口永久消失」场景会一直每分钟留一条 GAP-RETRY(预期行为,显式 `close` 即停)。 - 台架实测时 COM3 挂着在跑的板子(`[PROF]` 流),测试只做开关口/杀 daemon,未烧录未碰板。 Fixes #75
super_agent_bot added 1 commit 2026-11-04 11:11:50 +08:00
fix(serial-assistant): daemon 重启自动重开口 + 自动重连 + 日志缺口标记(#75)
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m19s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 44s
CI / 静态门禁 (pre-commit) (push) Failing after 4m51s
CI / 主机侧单测 (MSVC) (push) Failing after 43s
dfd1528409
按 Gitea issue #75 R1–R5 修采集层「daemon 死了采集静默停摆」族问题:
旧行为下 daemon 崩溃/被杀/读错丢口后,新实例起得来但口不开
(run() 先 _save_meta 把现场覆成 null,且无任何重开路径),
serial.log 缺口无边界,wait-idle 计满还良性返回 no_data,
把「采集器死」伪装成「板子静默」。

- R1 启动自动重开:meta.json 的 port 字段升格为「开口意图」现场
  (缺口期不清空,显式 close/stop 清除);run() 先读旧 meta 再覆写;
  重开在串口线程内执行(串口对象唯一持有者不变量),首次 = 出生 +1.5s。
  restart 特判:cmd_start(force) 在 stop 前抓 /status 现场、新 daemon
  就绪后 POST /open 开回来(stop 语义不变,结果进返回 JSON 的 reopen 键)。
- R2 失败自动重连:_port_error 后退避重试(REOPEN_SETTLE_S=1.5 起步、
  倍增、REOPEN_BACKOFF_MAX_S=30 封顶)直到成功或显式 close;
  逐次失败原文进 daemon.log(dlog=stderr),不刷 serial.log。
- R3 缺口标记:serial.log 落 [SYS] 行——daemon started(新旧 pid +
  重开现场)/ GAP-START(丢口与崩溃恢复两种,窗口起点下限声明)/
  GAP-END(窗口秒数 + 尝试次数 + 字节不可找回)/ GAP-RETRY(退避期
  仅首次 + 之后每分钟 ≤1 条,口径定死并写入设计说明 §6.1)。
- R4 状态可见:status 新增 auto_reopen{enabled,pending,attempts,
  last_error,next_retry_in_ms};port 新增 desired(意图)与 ready
  (实际)并列,缺口期 name/baud 显示待重开参数;wait-idle 入口加
  无口门:口未开秒回 ok:false + 现场,不再计满返回良性 no_data。
- R5:VERSION=1.2.0-skill;selftest 新增 T10–T15 共 22 项无头用例
  (重开意图判定 / 退避倍增封顶 / 缺口行格式 / 无口门 / auto_reopen
  三态 / restart 捕获),合计 45 项全绿;SKILL.md 与使用指南、
  设计说明 §6.1+§13 同步(机理只在设计说明详述一处)。

兼容性:JSON 只增字段、退出码形状不变;run_boot_test.py 正常路径
不受影响,其 wait-idle 撞无口时按采集错误 exit 2 归因(正是工单
要的区分)。

验证:selftest 45/45 PASS;COM3 台架三场景全过(崩溃恢复自动重开
+ GAP 标记、显式 close 后 restart 不重开、wait-idle 快速失败),
transcript 见工单汇报。
Author
Collaborator

This is by an automated agent, not a human.

CI 归因:远程「静态门禁 (pre-commit)」与「主机侧单测 (MSVC)」两个 push 任务在本 PR head dfd1528 上为 failure,但同样在 master 当前尖 f8c9296(#74 合并提交)上 failure——属 runner 环境既有问题,与本 PR 的 4 个文件(serial-assistant skill + 两份文档 + SKILL.md,无固件)无关;pull_request 形态的「主机侧单测 (MSVC)」在本 head 上为 success。提交期本地门禁 49 根全部 Passed(含 gitlint / doc-links / skill-size / no-mojibake)。

This is by an automated agent, not a human. CI 归因:远程「静态门禁 (pre-commit)」与「主机侧单测 (MSVC)」两个 push 任务在本 PR head `dfd1528` 上为 failure,但**同样在 master 当前尖 `f8c9296`(#74 合并提交)上 failure**——属 runner 环境既有问题,与本 PR 的 4 个文件(serial-assistant skill + 两份文档 + SKILL.md,无固件)无关;pull_request 形态的「主机侧单测 (MSVC)」在本 head 上为 success。提交期本地门禁 49 根全部 Passed(含 gitlint / doc-links / skill-size / no-mojibake)。
wandering1 merged commit c1beec50e7 into master 2026-11-04 11:39:11 +08:00
wandering1 deleted branch improve-serial-assistant-8e959e85113d 2026-11-04 11:39:13 +08:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wandering1/STM32F4-Base#76