Skip to content

[code-review] Knight Status/CanPerformTask read k.running/k.lock/k.cfg without mu — data race vs Start/Stop/Enable #2757

Description

@topcheer

文件和行号

internal/knight/scheduler.go:Status() 行 497-517、CanPerformTask() 行 526-532;写者对照 Start() 行 190-200、Stop() 行 212-230、Enable() 行 241-249、正确示范 Running() 行 232-237

问题描述

Start()/Stop()/Enable() 在 k.mu 锁内写 k.running、k.lock、k.cfg.Enabled;Running() 正确持锁读。但 Status()(行 502 if !k.running、行 503 if k.lock == nil、行 499 k.cfg.Enabled)和 CanPerformTask()(行 528)全部无锁读同一批字段。Go 内存模型意义上的数据竞态,-race 构建必报。

触发场景

独立复核(sa-100)确认三组跨 goroutine 调用点:

  1. WebUI 轮询 Status()(cmd/ggcode/daemon.go:1300-1308 HTTP handler goroutine)vs daemon 控制命令调 Start()/Stop()(daemon.go:975/983 另一 handler goroutine)—— 周期性轮询与 enable/stop 并发是常规路径
  2. TUI 渲染 goroutine 调 Status()(internal/tui/knight_panel.go:372)vs TUI 退出 defer knightAgent.Stop()(cmd/ggcode/root.go:782)
  3. 工具执行 goroutine 调 CanPerformTask()(PerformSkillAnalysis 路径)vs Stop

本仓库历史上专门修过同文件同类 -race 报警(v1.2.10 analyzedSessions、issue #977),并发调用假设成立。

预期行为 vs 实际行为

预期:所有共享字段读写走 k.mu(同 Running())。
实际:Status()/CanPerformTask() 无锁读,与持锁写构成数据竞态;-race 环境报 DATA RACE,语义上可能显示过期状态(如 k.lock == nil 判定给出错误的 "deferred/stopped")。

修复建议

Status()/CanPerformTask() 用 k.mu 包住对 k.running/k.lock/k.cfg.Enabled 的读取(或提取持锁的 runningLocked() 辅助函数)。注意 Status() 内 k.budget.Used() 若内部也持 k.mu 需在锁外调用,避免重入死锁。

严重程度

medium(无逻辑损坏,但违反项目 race-clean 标准,-race 用户环境必报;同文件有先例修复)

Activity

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