sched-ext discussion
[PATCH] selftests/sched_ext: Handle CPU hotplug write failures
LLM 分析
sched_ext selftests:修复 CPU hotplug 写失败导致的挂死
系列概况
- 标题:[PATCH] selftests/sched_ext: Handle CPU hotplug write failures
- 作者:Tianyi Chen hi@tychen.cc
- 版本:单版本(无 vN 编号,1/1)
- 规模:1 个文件,+23 / -11
- 修改文件:
tools/testing/selftests/sched_ext/hotplug.c - Message-ID:20260906135534.749534-1-hi@tychen.cc
- 完整性:包含 commit message(Fixes / Assisted-by / Signed-off-by)、diff 和测试方法说明;信息齐全。
补丁目的
让 tools/testing/selftests/sched_ext/hotplug.c 在 CPU 上下线写文件失败时不再傻等。
selftests 中对 sched_ext 热插拔的测试要切换 CPU 的 online 状态来触发调度器退出。原实现中 toggle_online_status() 只把错误 fprintf 到 stderr 就返回 void,不返回错误码,也不中止测试。一旦对 /sys/devices/.../online 的写入失败(权限、忙、EIO 等),测试会一直在 while (!UEI_EXITED(...)) 里转圈,等一个永远不会触发的 scheduler exit。
修复目标:
- 把错误码向上层传。
- 在关键 CPU 状态切换失败时立即中止测试(并清理 skel/link)。
- 把默认返回值从
SCX_TEST_PASS改成SCX_TEST_FAIL,避免"路径走到一半就算 pass"。
旧流程的问题
toggle_online_status() 是 void,调用方完全无视写入失败;测试主体在 CPU 在线/离线状态没切对的情况下继续运行 bpf_map__attach_struct_ops()、挂起等待 UEI_EXITED。失败信息只在 stderr 里,没人检查,CI 上表现为超时挂死。
另外 test_hotplug() / test_hotplug_attach() 的 status 初值是 SCX_TEST_PASS,因此即便路径中途出问题(甚至 goto 出去),老代码也会"返回 PASS"——一个隐藏的假阳性。
新流程
把 toggle_online_status() 改成 int,返回 file_write_long() 的结果;把两处测试函数的入口清理和结束分支改成 goto-out 模式:写入失败直接 goto out_destroy_skel / goto out_destroy_link,让既有的 cleanup() 统一回收资源(destroy link、destroy skel、再次 toggle_online_status(1) 复位)。status 初值置为 SCX_TEST_FAIL,只有走完 happy path 才显式置 SCX_TEST_PASS。这样原先"日志里报失败、但测试返回 PASS"的窗口被关掉。
+-------------------------------------+
| test_hotplug() entered |
| status = SCX_TEST_FAIL |
+-----------------+-------------------+
|
v
toggle_online_status(0) ?
+--------+--------+
|ok |fail
| v
v out_destroy_skel
attach + |
wait_for_exit() v
| cleanup() + return status
v
toggle_online_status(1) ?
+--------+--------+
|ok |fail
| v
v out_destroy_link
status = |
SCX_TEST_PASS v
| cleanup() + return status
v
out_destroy_link:
out_destroy_skel:
|
v return status
Patch 概览
只改一个文件:tools/testing/selftests/sched_ext/hotplug.c。
主要变化:
toggle_online_status():void->int,补一个return ret;,顺手补\n。test_hotplug():把关键toggle_online_status()的两次调用改为if (... toggle_online_status(...)) goto out_*;引入out_destroy_link/out_destroy_skel两个标签;status默认SCX_TEST_FAIL,happy path 显式置SCX_TEST_PASS。test_hotplug_attach():同上套路的两次toggle_online_status()改写;同样的 out 标签和status初值。
关键实现
核心改动是函数签名与返回值的传递:
static int toggle_online_status(bool online)
{
long val = online ? 1 : 0;
int ret;
ret = file_write_long(online_path, val);
if (ret != 0)
fprintf(stderr, "Failed to bring CPU %s (%s)\n",
online ? "online" : "offline", strerror(errno));
return ret;
}
调用点:
if (onlining && toggle_online_status(0))
goto out_destroy_skel;
...
if (toggle_online_status(onlining ? 1 : 0))
goto out_destroy_link;
...
if (!onlining && toggle_online_status(1))
goto out_destroy_link;
status = SCX_TEST_PASS;
out_destroy_link:
out_destroy_skel:
return status;
把 status 默认值设为 FAIL,意味着任何"没显式 status = PASS 就出函数"的路径都会失败——包括后续再加新分支、写错 goto 标签的情况。这是把"沉默正确"改成"沉默失败"的好做法。
类比
这相当于一个电梯门的安全回路:原来按钮按下去但门被卡住时,系统只是在日志里写一行"门没动",然后原地等乘客走出来——永远不会有人走出来。新版本让按钮直接告诉调度中心"门没动",调度中心立刻进入恢复流程(释放 BPF 资源、把 CPU 重新拉回在线),并明确把这趟行程标记为"不成功",而不是算"运行正常"。把默认结果从 PASS 改成 FAIL,等于把电梯出厂设定从"默认准点"改成"默认故障",只有完全跑完一遍才发现问题才算准点。
另一个类比:厨房烤箱的温度传感器失灵——老代码会读到旧温度继续烤,新代码会立刻跳到安全清理(关火、开门散温),并把这一炉记为失败,让维护人员去检查传感器。
Highlight:风险与注意点
cleanup()自己还会再调一次toggle_online_status(1)。如果正常路径上 CPU1 复位失败,现在也会通过status报 FAIL——这是作者刻意加入的"normal CPU restoration 失败也要 fail"逻辑,但需要确认cleanup()对toggle_online_status的返回值仍是无害地忽略(diff 没有显示cleanup()改动,假定它仍然不关心)。Assisted-by: LLM出现了——审阅时应留意 LLM 生成的 goto 标签是否覆盖了所有原本隐式 fall-through 的清理路径,特别是test_hotplug()里"attach 之后、wait 之前"的那段代码,看是否漏掉了bpf_link__destroy。- 既有
cleanup()自身不感知新返回值——如果cleanup()内部又失败一次,目前没有第二层兜底;这是一个未来可以补的"显式 propagation"。 - 测试验证方法依赖 strace 注入 EIO,是白盒负向测试;建议在 CI 上也跑一次"没有注入"的 baseline,确认改动没引入误报。
- 关联代码:
scx_test_status、hotplug__destroy、bpf_link__destroy——这些都是项目内既有宏/函数,patch 不引入新依赖,回归面较小。
版本变化
单封 patch,无 v2/v3 演进。
一句话总结
把 sched_ext hotplug selftest 中 toggle_online_status() 从"只打印"改成"返回错误码",并在两次关键 CPU 状态切换失败时通过 goto 跳出测试,把默认返回值从 PASS 改为 FAIL,从而消除"CPU 写失败 → 测试挂死或假阳性 pass"的窗口。