macOS: 合盖后虚拟机继续运行导致发热
问题描述
Summary: MacBook 合盖(关闭盖子)后,Multipass 虚拟机继续运行,导致设备发热和电池快速耗尽。
Severity: High - 影响用户体验和设备安全
Platform: macOS (affecting both Intel and Apple Silicon)
Affected Versions: All versions up to and including 1.16.3
复现步骤
- 在 MacBook 上安装 Multipass 1.16.3
- 启动一个或多个实例:
multipass launch
- 合上 MacBook 盖子(clamshell mode)
- 等待几分钟到几小时
- 观察设备底部温度、风扇噪音、电池电量消耗
预期行为
合盖后,Multipass 应该自动挂起所有运行中的虚拟机,以节省电量并避免发热。
实际行为
虚拟机继续全速运行,导致:
- 设备持续发热,底部温度可达 40-50°C
- 风扇高速运转(如有)
- 电池在睡眠状态下快速耗尽
- 用户携带设备时感觉明显发热
根因分析
经过代码排查,发现以下问题:
1. 守护进程未订阅 macOS 系统睡眠通知
src/daemon/daemon_main.cpp 中的 UnixSignalHandler 仅处理 SIGTERM、SIGINT、SIGUSR1 三种 Unix 信号,未订阅 NSWorkspaceWillSleepNotification 或 IORegisterForSystemPower 等系统睡眠事件。
相关代码:
// daemon_main.cpp:47-77
class UnixSignalHandler
{
void monitor_signals(sigset_t sigset)
{
int sig = -1;
sigwait(&sigset, &sig);
// 仅处理 SIGTERM/SIGINT/SIGUSR1
if (sig != SIGUSR1)
mpl::info("daemon", "Received signal {} ({})", sig, strsignal(sig));
app_ready_signal.wait();
QCoreApplication::quit();
}
};
2. QEMU/AppleVZ 虚拟 CPU 线程不受睡眠约束
src/platform/backends/qemu/macos/qemu_platform_macos.cpp 使用 -accel hvf(Hypervisor Framework)启动虚拟机。HVF 创建的虚拟 CPU 线程在宿主机上以实时调度优先级运行,不受 macOS clamshell sleep 约束。
相关代码:
// qemu_platform_macos.cpp:83
qemu_args << "-accel" << "hvf";
3. 无自动挂起机制
src/daemon/daemon.h 中没有"系统睡眠时自动挂起实例"的机制,用户必须手动执行 multipass suspend 或 multipass stop。
建议的解决方案
核心功能
实现 "合盖自动挂起实例" 功能,包括:
-
新增 SleepWakeHandler 组件
- 订阅
NSWorkspaceWillSleepNotification 和 NSWorkspaceDidWakeNotification
- 收到睡眠通知时调用
Daemon::suspend_all_instances()
- 收到唤醒通知时调用
Daemon::resume_suspended_instances()(可配置)
-
新增两个设置项
local.auto-suspend-on-sleep(bool,默认 true):控制是否在系统睡眠时自动挂起实例
local.auto-resume-on-wake(bool,默认 false):控制是否在系统唤醒时自动恢复实例
-
扩展 Daemon 类
- 新增
suspend_all_instances() 方法:挂起所有 running 状态的实例
- 新增
resume_suspended_instances() 方法:恢复之前自动挂起的实例
- 记录自动挂起的实例名称,避免唤醒后重复启动用户手动挂起的实例
文件变更
| 文件 |
变更类型 |
src/platform/backends/macos/sleep_wake_handler.h |
新建 |
src/platform/backends/macos/sleep_wake_handler.mm |
新建 |
include/multipass/constants.h |
修改(新增设置常量) |
src/daemon/daemon.h |
修改(新增方法声明) |
src/daemon/daemon.cpp |
修改(实现新方法) |
src/daemon/daemon_main.cpp |
修改(初始化 handler) |
src/daemon/daemon_init_settings.cpp |
修改(注册设置项) |
src/platform/backends/macos/CMakeLists.txt |
修改(添加源文件) |
用户接口
# 启用/禁用自动挂起
multipass set local.auto-suspend-on-sleep=true # 默认值
# 启用/禁用自动恢复
multipass set local.auto-resume-on-wake=false # 默认值
替代方案
用户端临时缓解措施
在官方实现前,用户可以使用以下方法缓解:
- 手动挂起: 合盖前执行
multipass suspend --all
- 使用 sleepwatcher: 安装
sleepwatcher 并配置脚本:
# ~/.sleep
/usr/local/bin/multipass suspend --all
# ~/.wakeup
/usr/local/bin/multipass start --all
- 使用 launchd: 创建 LaunchAgent 监听系统睡眠事件并执行脚本
macOS caffeinate(不推荐)
使用 caffeinate 可以防止系统睡眠,但这会加剧发热问题,不适用于此场景。
详细设计文档
完整的架构设计和实施计划请参考:
相关 Issue
测试计划
单元测试
SleepWakeHandler 的回调触发
Daemon::suspend_all_instances() 的正确性
- 设置项读取/写入
集成测试
- 合盖后验证实例状态变为
suspended
- 开盖后验证实例状态恢复为
running(如果启用)
- 日志中确认收到睡眠/唤醒通知
手动测试
- 在真实 MacBook 上合盖,观察发热情况
- 使用
pmset -g log 查看系统睡眠事件
- 使用
multipass info 确认实例状态
环境信息
Multipass version: 1.16.3
macOS version: macOS Sequoia 15.x / macOS Sonoma 14.x
Architecture: Apple Silicon (M1/M2/M3/M4) and Intel
Driver: QEMU (default) / AppleVZ
Labels: bug, macOS, enhancement, good first issue, help wanted
macOS: 合盖后虚拟机继续运行导致发热
问题描述
Summary: MacBook 合盖(关闭盖子)后,Multipass 虚拟机继续运行,导致设备发热和电池快速耗尽。
Severity: High - 影响用户体验和设备安全
Platform: macOS (affecting both Intel and Apple Silicon)
Affected Versions: All versions up to and including 1.16.3
复现步骤
multipass launch预期行为
合盖后,Multipass 应该自动挂起所有运行中的虚拟机,以节省电量并避免发热。
实际行为
虚拟机继续全速运行,导致:
根因分析
经过代码排查,发现以下问题:
1. 守护进程未订阅 macOS 系统睡眠通知
src/daemon/daemon_main.cpp中的UnixSignalHandler仅处理SIGTERM、SIGINT、SIGUSR1三种 Unix 信号,未订阅NSWorkspaceWillSleepNotification或IORegisterForSystemPower等系统睡眠事件。相关代码:
2. QEMU/AppleVZ 虚拟 CPU 线程不受睡眠约束
src/platform/backends/qemu/macos/qemu_platform_macos.cpp使用-accel hvf(Hypervisor Framework)启动虚拟机。HVF 创建的虚拟 CPU 线程在宿主机上以实时调度优先级运行,不受 macOS clamshell sleep 约束。相关代码:
3. 无自动挂起机制
src/daemon/daemon.h中没有"系统睡眠时自动挂起实例"的机制,用户必须手动执行multipass suspend或multipass stop。建议的解决方案
核心功能
实现 "合盖自动挂起实例" 功能,包括:
新增
SleepWakeHandler组件NSWorkspaceWillSleepNotification和NSWorkspaceDidWakeNotificationDaemon::suspend_all_instances()Daemon::resume_suspended_instances()(可配置)新增两个设置项
local.auto-suspend-on-sleep(bool,默认true):控制是否在系统睡眠时自动挂起实例local.auto-resume-on-wake(bool,默认false):控制是否在系统唤醒时自动恢复实例扩展 Daemon 类
suspend_all_instances()方法:挂起所有running状态的实例resume_suspended_instances()方法:恢复之前自动挂起的实例文件变更
src/platform/backends/macos/sleep_wake_handler.hsrc/platform/backends/macos/sleep_wake_handler.mminclude/multipass/constants.hsrc/daemon/daemon.hsrc/daemon/daemon.cppsrc/daemon/daemon_main.cppsrc/daemon/daemon_init_settings.cppsrc/platform/backends/macos/CMakeLists.txt用户接口
替代方案
用户端临时缓解措施
在官方实现前,用户可以使用以下方法缓解:
multipass suspend --allsleepwatcher并配置脚本:# ~/.wakeup /usr/local/bin/multipass start --allmacOS caffeinate(不推荐)
使用
caffeinate可以防止系统睡眠,但这会加剧发热问题,不适用于此场景。详细设计文档
完整的架构设计和实施计划请参考:
相关 Issue
测试计划
单元测试
SleepWakeHandler的回调触发Daemon::suspend_all_instances()的正确性集成测试
suspendedrunning(如果启用)手动测试
pmset -g log查看系统睡眠事件multipass info确认实例状态环境信息
Labels:
bug,macOS,enhancement,good first issue,help wanted