fix(skillfs): preserve SLS logs on panic#1508
Conversation
There was a problem hiding this comment.
- SlsOpsGuard 未覆盖 logging 初始化阶段的潜在 broken pipe panic,可能仍遗漏早期 SLS 记录。
- validate 子命令通过 finish_sls 使用 guard 的方式缺乏类型约束,未来改动易引入重复记录。
- 回归测试中使用 unsafe libc 管道操作时错误处理和 fd 清理不够严谨,建议收紧安全边界。
🤖 Generated by Qoder • View workflow run
- Arm an RAII SlsOpsGuard in main before logging init, so a broken stdout/stderr pipe closing before the first write still logs one record. - finish() writes eagerly and disarms, keeping validate's pre-exit record that a Drop-only guard would lose to process::exit skipping destructors. - Covers list/classify/validate/mount and panic unwinding only, not abort/SIGKILL, which never run drops. Signed-off-by: kongche-jbw <kongche.jbw@alibaba-inc.com>
c07e455 to
ceccf7b
Compare
|
已在 ceccf7b 中处理文件描述符清理问题。 pipe() 成功后会立即将两个文件描述符包装为 OwnedFd,dup() 这里没有增加 EINTR 重试或引入 nix 依赖:该逻辑仅用于测试, 已使用 Rust 1.86.0 重新验证:
|
| /// Reproduce `skillfs <cmd> 2>&1 | <reader that closes immediately>` (the | ||
| /// issue's own repro). Creates a pipe and closes its read end *before* | ||
| /// spawning, so the child inherits a reader-less pipe on both stdout and | ||
| /// stderr. Its very first write — the logging line emitted in `main` before |
There was a problem hiding this comment.
此处(以及 SlsOpsGuard 文档注释 main.rs:542-543)的论证与实际行为略有出入。
注释称"child 的第一次写入 —— main 中在命令体之前发出的日志行 —— 命中 EPIPE 并 panic"。但 main 里的 info!(pid, "starting skillfs CLI") 是经由 tracing 的 fmt subscriber 写到 stderr 的,而 tracing 的 writer 会吞掉写入错误(EPIPE),并不会 panic。因此合并输出场景下真正触发 panic 的并不是这行日志。
实际情况:
list/classify/validate:panic 发生在命令体里的第一个println!。mount:panic 发生在cmd_mount里挂载前的eprintln!提示横幅(main.rs:1826-1889),恰好在 FUSE 挂载调用之前——这也是该用例"不残留挂载"性质的实际来源。
测试断言(1 条记录、err_reason="panic")依然正确,但"logging line ... hits EPIPE and panics"的措辞会误导读者,且 mount 用例"不残留挂载"其实隐式依赖"横幅在挂载之前"这一顺序。建议把注释改为描述实际的 panic 位置,或直接删掉对具体 panic 点的断言性描述。
在 logging 初始化前 arm guard 这一做法本身是对的(对未来新增的 stdout 直写是保险),此点无需改动。
Description
SkillFS
list,classify,validate, andmountcan lose their SLS opsrecords when a downstream pipe closes before
log_commandis reached. Thischange arms a single RAII
SlsOpsGuardimmediately after CLI parsing, beforetracing emits output, and finishes it eagerly on normal or error returns.
Panic unwinding records
err_reason="panic", whilevalidatepreserves itsrecord before
process::exit. Deterministic reader-less pipe tests cover bothstdout-only and merged stdout/stderr failure paths.
Related Issue
closes #1506
Type of Change
Scope
cosh(copilot-shell)cosh-ng(cosh-ng)sec-core(agent-sec-core)skill(os-skills)sight(agentsight)tokenless(tokenless)ckpt(ws-ckpt)memory(agent-memory)anolisa(anolisa-cli)skillfs(SkillFS)Checklist
cosh: Lint passes, type check passes, and tests passcosh-ng:cargo clippy --all-targets -- -D warningsandcargo fmt --checkpasssec-core(Rust):cargo clippy -- -D warningsandcargo fmt --checkpasssec-core(Python): Ruff format and pytest passskill: Skill directory structure is valid and shell scripts pass syntax checksight:cargo clippy -- -D warningsandcargo fmt --checkpasstokenless:cargo clippy -- -D warningsandcargo fmt --checkpassmemory(Linux only):cargo clippy --all-targets -- -D warnings,cargo fmt --check, andcargo testpassanolisa:cargo clippy --all-targets --locked -- -D warnings,cargo fmt --all --check, andcargo test --lockedpassskillfs:cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo test --workspacepasspackage-lock.json/Cargo.lock)Testing
Executed on Linux with Rust 1.86.0 and the system FUSE 3 development package:
The FUSE smoke suite performed real mounts and verified unmounting, managed
supervisor recovery, orphan cleanup, and fast-failure cleanup. No FUSE checks
were skipped.
Additional Notes
abortandSIGKILLcannot run destructors.StopandSuperviseremain internal commands without SLS ops records.