[Bug] 显示器休眠/唤醒后 Mos 后台崩溃退出(CVDisplayLink 线程 use-after-free) · Issue #1019 · Caldis/Mos · GitHub
Skip to content

[Bug] 显示器休眠/唤醒后 Mos 后台崩溃退出(CVDisplayLink 线程 use-after-free) #1019

Description

@skiuniverse

Bug 描述

我在 Mac 上后台运行 Mos 时,偶尔出现以下现象:

  1. 显示器主动休眠突然黑屏
  2. 唤醒后马上再次黑屏
  3. 过一段时间才恢复正常;
  4. 期间后台的 Mos 进程退出(鼠标滚轮方向恢复 macos 默认)。

现象集中在显示器休眠 / 唤醒阶段出现,并非每次必现,而是偶发。

环境信息

  • Mos 版本:4.2.1
  • macOS 版本:26.x
  • 机型显示器:外接屏 + 原厂屏
  • 辅助功能权限:已授予
  • 鼠标:普通滚轮鼠标(平滑滚动 / 反向均开启)

说明:已在 4.0.1 及以上复现。因此不是 4.0.0 时代那个"post 路径重复释放"的已知崩溃(那个已被 commit 703fad6 修复)。

根因分析

这一崩溃与已有的两个 issue 类似:

根因代码Mos/ScrollCore/ScrollPoster.swift):

poster 这个属性:

private var poster: CVDisplayLink?   // CoreVideo 管理的 CF 对象

它被两个线程无锁并发访问,而 ScrollPoster 里的 stateLock 只保护滚动状态(current/buffer/delta/filter/ScrollPhase),并不保护 poster

  • 主线程:显示器变化/休眠唤醒时,经 NSApplication.didChangeScreenParametersNotificationrecreateDisplayLink()create(),会 CVDisplayLinkStop(旧 poster) + poster = nil 再建新的;sessionDidResigndisable()stop() 也会停止它。
  • CVDisplayLink 回调线程:滚动手势结束时,processing() 调用 stop(),内部对 posterCVDisplayLinkStop(poster)

两条线程对同一个 CF 对象并发"停止 + 释放",破坏其引用计数,最终在 CoreVideo 内部 _CFRelease 时崩溃。

为什么在"显示器黑屏/唤醒"时最易触发:因为那时主线程最可能在 create()/recreateDisplayLink()didChangeScreenParametersNotification 触发),而回调线程恰好结束一次滚动(stop()),两者在无锁的 poster 上碰撞,竞态窗口闭合。

补充:posterstop() 里是在拿 stateLock 之前就读取的(if let validPoster = poster { CVDisplayLinkStop(validPoster) }),所以这段访问完全不受锁保护。

修改建议

核心思路:poster(及 CVDisplayLinkStop)只在主线程被访问

  1. processing()(CVDisplayLink 回调线程)不要在回调里直接调用 stop(),改为派发到主线程:
    DispatchQueue.main.async { [weak self] in
        self?.stopIfCurrent(phase, generation: gestureGeneration)
    }
  2. 由于 stop 被延迟到主线程执行,需要防"过期 stop":新增一个滚动手势代次计数(每次 update() 递增),在 stopIfCurrent 里判断代次未变才真正执行,避免一个延迟的 stop 误清用户刚开启的新手势、停掉 link。
  3. 可选防御:给 poster 加一把独立锁(区别于 stateLock),把所有访问 poster 的路径统一串行化。

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