Skip to content

[code-review] IsProcessAlive treats signal-0 EPERM as dead on Unix - asymmetric with the Windows ACCESS_DENIED->alive fix (#1723/#1535); false-death deletes PID files and forks a second daemon or triggers concurrent crash recovery (#1490 same lesion, Unix side) - one-line errors.Is fix #2190

Closed

Description

@topcheer

文件和行号

internal/util/process.go L26-28(IsProcessAlive)

问题:Unix 侧 signal-0 的 EPERM 被判为"已死"——与 Windows 侧 ACCESS_DENIED→alive 的修复不对称,误判死可致双 daemon/并发恢复

证据链(sa-151 初审中 → sa-152 独立复核确认中低):

  1. process.go:26-28:if proc.Signal(syscall.Signal(0)) != nil { return false }——Unix kill(pid,0) 的 EPERM 语义 = 进程存在但无信号权限(POSIX)。当前把 EPERM 与 ESRCH 同等判死
  2. 平台不对称坐实:Windows 孪生 process_windows_helper.go:27-38([business-logic] #1535 fixed the Unix EPERM case but the Windows helper returns true for ALL OpenProcess errors - a dead PID gets ERROR_INVALID_PARAMETER(87), not ACCESS_DENIED(5), so a crashed daemon is judged alive and EnsureDaemonSlot is permanently wedged; the existing TestIsProcessAlive_NonExistent would go red on Windows, proving the Verified claim never ran Windows tests #1723/[code-review] both platform aliveness checks treat permission errors as death: unix Signal(0) returns EPERM for a live root-owned daemon and windows OpenProcess returns ERROR_ACCESS_DENIED for elevated processes - CheckExistingDaemon deletes the PID file either way and a second daemon gets forked; TerminalFollowDisplay.OnError never clears roundBuf though the bridge-side of the same leak was already fixed in #552-D; Windows daemon is the repo's only long-lived child missing CREATE_NO_WINDOW (dies on Ctrl+C); darwin argv-parse failure falls back to the pre-#31 whole-blob-with-ENV match #1535)专门把 ACCESS_DENIED 判 alive,注释记载"误判死会删 PID 文件并 fork 第二个 daemon",仅 ERROR_INVALID_PARAMETER 判死,"when in doubt, alive"
  3. 调用点后果(均读源码核实):
  4. 可达性:需跨用户/提权分裂(sudo daemon + 非 root 检查、或 root 拥有的状态目录被非 root 读)——非常规但真实;EPERM 下 isZombieUnix 读不了他户 /proc 返回 false(保守判活),与新语义自洽

触发场景

root/sudo 启动 ggcode daemon → 非 root 用户的 ggcode 调 IsProcessAlive → EPERM → 判死 → 删 PID 文件 + fork 第二实例;或 run journal 被误判 crash 走并发恢复。

预期行为 vs 实际行为

  • 预期:EPERM ⟹ 存在(与 Windows "when in doubt, alive" 对齐)
  • 实际:EPERM ⟹ 判死

修复建议(一行)

if err := proc.Signal(syscall.Signal(0)); err != nil && !errors.Is(err, syscall.EPERM) {
    return false
}

严重程度

medium-low(三先例背书应修:#1490/#1535/#1723 均为同病灶 Windows 侧事故/修复;Unix 侧触发罕见但后果是双实例/并发恢复竞争)

(R72 轮:sa-151 初审 → sa-152 独立复核【确认中低——降半级理由:Unix 跨用户场景罕见】。同轮 P2(symlink 回退删文件)被复核证伪为不可达(debug.go:882 前置 Remove 断危害链)降为加固项不立案;P3(shell 探测 sync.Once 永久缓存失败)低不立案。全程只读。查重:#1723/#1535/#1405 已关——本条是 Unix 侧对称缺口新角度)

Activity

  1. topcheer commented on Sep 13, 2026

    @topcheer
    OwnerAuthor

    已修复并合并:PR #2195(squash 合并,reviewer 复核通过;裁定见 https://github.com/topcheer/ggcode/pull/2195#issuecomment-5651146387)。

    修复内容:kill(pid,0) 的 EPERM 改保守判活(!errors.Is(err, syscall.EPERM) 一行)——Unix 侧 #1723 孪生病灶收口:跨权限探测不再误判死,instance_detect PID 清理与 run_journal crash 恢复不再误触发(防 #1490 类二次 daemon fork)。

    复核链:EPERM+僵尸组合两子情形拆解(/proc 可读时僵尸检测精度保留;不可读时落保守活)——「when in doubt, alive」与 Windows #1723 原则对称;process.go 为 !windows 独占文件平台分裂干净;EPERM 分支单测不可构造的诚实说明+周围语义钉(沙箱按设计 SKIP)→ IsProcessAlive 族 5 测+util 全量 → CI 绿 → 合并。关闭。

  2. topcheer commented on Sep 13, 2026

    @topcheer
    OwnerAuthor

    复核结论:真(sa-151 初审中 → sa-152 独立复核确认中低——POSIX EPERM 语义 + Windows 孪生 #1723 不对称 + instance_detect/run_journal 两调用点后果 + #1490 同类事故史全链成立),PR #2195 已合并修复,关闭。

    修复:IsProcessAlive 的 signal-0 判定改为"仅真实错误(ESRCH 等)判死"——EPERM(进程存在但无信号权限)走保守判活,与 Windows 侧 "when in doubt, alive" 原则及 isZombieUnix 的保守策略自洽(EPERM 下读不了他户 /proc,僵尸检查自然回退判活)。跨权限探测(sudo daemon + 非 root 检查)不再触发误删 PID 文件/起第二实例/误判 crash 并发恢复。

    验证:EPERM 分支需跨权限进程对(单测不可构造——注释说明);周围语义(self-alive / reaped-child-dead)回归钉住(!windows build tag——首轮 CI 的 Windows process-liveness 作业编译 Unix-only syscall 暴露,已修)+ util 全量 + 全仓 build;PR #2195 全 checks 绿后 reviewer squash 合并。

  3. topcheer commented on Sep 13, 2026

    @topcheer
    OwnerAuthor

    #2190 修复验收(a2822b3b)——过

    三条信息级备注(不阻塞)

    a. background_unix.go:35 daemon 侧用 err == syscall.EPERM 直接等值比较,与本次 errors.Is 形态分裂——当前 Go 返回裸 Errno 不触发,前向兼容脆弱(预存,非本 commit 引入)。建议后续顺手统一为 errors.Is
    b. hidepid=2 下他户僵尸被报活——保守方向(保留状态),与 #1839 fail-safe 设计一致,属预期
    c. EPERM 分支零直接覆盖(需跨特权进程对)——commit message 已诚实声明;如需可仿 windows-real-machine-verify 在带 sudo 的 Actions 加探针

    (R76 验收轮 sa-158 全程只读)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions