fix(ostool-server): isolate serial and websocket I/O with bounded queues - #185
Conversation
There was a problem hiding this comment.
本 PR 将 ostool-server 的串口 RX/TX 与 WebSocket RX/TX 拆成独立异步 worker,加入有界数据队列、命令大小限制、关闭清理逻辑,并同步中英文文档,目标是避免串口和网络 I/O 相互阻塞。改动集中在串口 transport/session 生命周期与说明文档;串口发现、QEMU/physical serial 分支及既有 WebSocket 控制协议仍沿用现有契约。
固定 head 4a873173f922947919d0de39e73988cdf5b272da 的两个 CI check runs 均为 completed/success,未发现由本 PR 引起的 CI 失败。本地按计划尝试了 cargo fmt --check、cargo clippy --manifest-path ostool-server/Cargo.toml --all-features -- -D warnings 和 cargo test --manifest-path ostool-server/Cargo.toml --all-features,但审查环境没有 cargo,三项均因 /bin/sh: cargo: not found 未能执行;这属于环境限制而非代码结论。此前没有 GitHub review 或 PR comment 需要处理。相似的 #77/#78 涉及会话释放生命周期,但没有发现与本 PR 重复的串口 transport 实现。
仍有一个阻塞问题:ostool-server/src/serial/transport.rs:54 在串口 EOF 后永久等待,而 WebSocket 写 worker 若卡在发送 queued frame,transport 就不会返回,导致外层无法及时合并串口、清理缓冲并释放 session lease。请见 inline comment;建议补充 EOF 与 WebSocket writer 的协调及有界排空回归测试。
Powered by gpt-5.6-luna
| if size == 0 { | ||
| // Let the WebSocket writer drain queued final bytes before ending. | ||
| drop(output_tx); | ||
| return pending::<anyhow::Result<()>>().await; |
There was a problem hiding this comment.
串口 EOF 后这里会永久 pending,而 run 只有在四个 worker 之一结束时才返回。如果此时 write_websocket 正卡在 ws_sender.send(...)(客户端停止读取但连接没有关闭),它也不会结束;外层只好等独立的会话过期路径介入,绕过文档承诺的一秒关闭上限;如果客户端仍发送 heartbeat,串口租约甚至可以一直保持。请让 EOF 进入协调器并给最后的 WebSocket 排空设置有限时限,超时也要退出 transport 触发外层清理。
问题与证据
排查 rcore-os/tgoskits#2357 的 OrangePi
native-hardware-smoke超时时,发现板卡 shell 和 NPU 命令仍能正常完成,但串口日志出现 NUL、截断和成功标记缺失。同一问题也出现在 TGOSKitsdev基线。服务主机上实测:
TIOCINQ接收队列达到 4095 字节,FTDITIOCGICOUNT.overrun从 288 增至 298;期间 frame/parity 没有新增。旧服务器在同一个循环中读取 64 字节后等待 WebSocket 发送、会话心跳,并在串口写入后调用flush()。tokio-serial的poll_flush底层同步调用tcdrain,还持有tokio::io::split的共享锁。上述等待都会使接收停止推进。这证明了服务端接收链路存在丢失及阻塞边界;尚不能仅凭计数把每次现场暂停归因于某一个具体 await。
改动及原因
tx命令限制为 256 KiB,整条检查后再入队,避免容量不足时只提交半条命令。tcdrain。关闭时以非阻塞查询观察发送队列,最多等 1 秒,超时也清除残留命令。WebSocket 关闭通知同样不能无限阻止租约回收。验证
确定性红绿:旧
write_serial_payload在禁止进入 UART drain 的同一回归中失败;移除运行期 drain 后通过。定向覆盖:阻塞 WebSocket 时继续接收串口、阻塞串口 TX 时继续输出及处理关闭、EOF 保留包括 NUL 在内的原始字节、超出缓冲容量的持续输出保持顺序、溢出显式报错、超大命令零字节提交、取消归还串口所有权、排空失败仍清理缓冲。
cargo test -p ostool-server:143 项单元测试、3 项真实 PTY/WebSocket 生命周期测试通过。cargo clippy -p ostool-server --target x86_64-unknown-linux-gnu --all-features、cargo fmt --all -- --check、release 构建通过。等现有板卡会话全部释放后,已替换测试服务器二进制;保留原二进制备份,未改配置和 systemd unit。
同一 OrangePi-5-Plus-3、原始内核及原始
native-hardware-smoke连续三轮通过(68.37、95.43、95.29 秒):首轮接收队列采样峰值 992 字节,后两轮合并峰值 496 字节;overrun/frame/parity 增量均为 0,三份日志 NUL 数均为 0。一次通过不足以证明所有历史停滞;这里同时保留原失败的计数证据及修复后的连续观测。正式
cargo xtask starry test board --board orangepi-5-plus --test-case native-network-smoke同板卡通过(51.14 秒),队列峰值 496 字节、overrun/frame/parity 增量及日志 NUL 均为 0。首次直接运行板卡入口因缺少预上传会话文件在启动前退出,未计为板卡失败或通过;随后使用测试入口准备依赖并完整执行。精确提交
4a873173f922947919d0de39e73988cdf5b272da的 push CI 和 PR CI 均 completed/success,覆盖完整工作区 clippy、构建、测试、Web UI 单元与集成测试、publish dry run。