Skip to content

[code-review] 14 个 IM panel 复发 #2337 live-map len 读:len(m.config.IM.Adapters) 绕过 imAdaptersMu,持锁写并发时 fatal #2796

Description

@topcheer

文件和行号

同型一行出现在 14 个 IM panel 的 *BindingEntries() 渲染路径(每帧执行):

  • internal/tui/discord_panel.go:418
  • internal/tui/dingtalk_panel.go:423
  • internal/tui/matrix_panel.go:483
  • internal/tui/whatsapp_panel.go:517
  • internal/tui/irc_panel.go:456
  • internal/tui/feishu_panel.go:446
  • internal/tui/slack_panel.go:493
  • internal/tui/signal_panel.go:654
  • internal/tui/tg_panel.go:400
  • internal/tui/mattermost_panel.go:472
  • internal/tui/nostr_panel.go:562
  • internal/tui/twitch_panel.go:447
  • internal/tui/wechat_panel.go:615
  • internal/tui/qq_panel.go:449

问题描述

keys := make([]string, 0, len(m.config.IM.Adapters)) 是这些文件中唯一绕过 imAdaptersMu 的 map 读。#2152 建立的契约是所有 IM adapter map 访问走锁访问器(IMSnapshot/GetIMAdapter/IMAdapterEnabled 均为 RLock+深拷贝)。互斥锁对不持锁读者零保护:与任一持锁写(SetIMAdapterEnabled / AddIMAdapter / toggle 等,config_save.go:679-698)交叠时 Go runtime fatal "concurrent map read and map write",不可恢复,整个 TUI 崩溃。

触发场景

  1. 打开任一 IM panel(每帧渲染执行该行)
  2. 同时任一 Cmd goroutine 完成一次持锁写(如 im_panel.go toggleIMAdapterEnabled 的 enable/disable 链、或残留的 Cmd goroutine 直调 SetIMAdapterEnabled 的路径)
  3. 写入期间恰好帧渲染到该行 → fatal

预期行为 vs 实际行为

修复建议

每个文件改为先取一次快照复用:snap := m.config.IMSnapshot().Adapters; keys := make([]string, 0, len(snap))(下一行的遍历本来就用 IMSnapshot(),可复用同一变量)。并将 #2337 的源码扫描测试泛化到全部 IM panel 文件,防止再次复发。

严重程度

low~medium(触发窗口窄:需要渲染帧与持锁写精确交叠;但后果是进程 fatal 不可恢复,且面板打开期间渲染每帧都执行该行)

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