news 2026/9/21 20:10:19

safe_sleep.sh: GitHub Actions Runner 中那个偶尔无限挂起的“小睡眠”脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
safe_sleep.sh: GitHub Actions Runner 中那个偶尔无限挂起的“小睡眠”脚本

在 GitHub Actions Runner 代码库里,有一个看似简单的 Bash 脚本——safe_sleep.sh,它负责让 Runner 在某些场景下“安全地睡眠”一段时间。但这个小脚本却因为一个 subtle 的逻辑缺陷,让许多开发者和 CI 系统管理员困扰不已,甚至引发了性能、资源浪费、Runner 卡死等严重问题。

一、什么是safe_sleep.sh

GitHub Actions Runner 是负责执行 CI/CD 工作流程的后台服务。某些流程中,Runner 需要暂停一段时间(例如等待自动更新完成),这时就会调用一个名为safe_sleep.sh的脚本。

看似是个简单任务:让程序睡眠 N 秒钟。但设计者并没有调用系统自带的sleep命令,而是用 Bash 语言编写了一个自循环脚本。

在旧版本中,脚本内容大致如下:

#!/bin/bash SECONDS=0 while [[ $SECONDS != $1 ]]; do : done

通过 busy-waiting 来实现睡眠,而不是调用标准sleep

二、问题核心:循环条件有 Bug

乍看这段代码好像没毛病,但它有一个细节严重依赖了调度时机

while [[ $SECONDS != $1 ]]; do …

这个循环假设SECONDS会按 0 → 1 → 2 → … 逐秒增加并且正好等于目标值。但在真实环境中:

  • 如果机器负载高;

  • 或者 Runner 进程被系统调度延迟;

  • 或者在虚拟化环境里暂停一段时间;

那么SECONDS有可能会直接跳过目标值,例如从 0 跳到 2。在这种情况下:

条件永远不会变成 “等于目标值”,循环就会永远执行下去 ——无限循环

三、实际观测到的问题

Very rarely on update of github actions runnersafe_sleep.shhangs forever:

$ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ... actions+ 72298 96.3 0.0 4372 3328 ? R Apr02 1988:54 /bin/bash /home/actions-runner/safe_sleep.sh 1 ...

详见:safe_sleep.sh rarely hangs indefinitely

四、影响案例:更多比预想严重

不仅仅是某个挂起的新 Runner,某些开源项目(例如 Zig)据反馈在自托管 Runner 上观察到了成堆的死循环实例,这些无限循环的safe_sleep.sh不仅占满了 CPU,还导致 Runner 服务无法正常运行数日之久。

五、修复版本

https://github.com/actions/runner/commits/main/src/Misc/layoutroot/safe_sleep.sh

1. 最新修复版本
#!/bin/bash # try to use sleep if available if [ -x "$(command -v sleep)" ]; then sleep "$1" exit 0 fi # try to use ping if available if [ -x "$(command -v ping)" ]; then ping -c $(( $1 + 1 )) 127.0.0.1 > /dev/null exit 0 fi # try to use read -t from stdin/stdout/stderr if we are in bash if [ -n "$BASH_VERSION" ]; then if command -v read >/dev/null 2>&1; then if [ -t 0 ]; then read -t "$1" -u 0 || :; exit 0 fi if [ -t 1 ]; then read -t "$1" -u 1 || :; exit 0 fi if [ -t 2 ]; then read -t "$1" -u 2 || :; exit 0 fi fi fi # fallback to a busy wait SECONDS=0 while [[ $SECONDS -lt $1 ]]; do : done
2. 执行逻辑(从“优雅”到“原始”)
(1)首选:sleep
if [ -x "$(command -v sleep)" ]; then sleep "$1" exit 0 fi
  • 检查sleep是否存在且可执行

  • 最准确、最省 CPU

  • 正常系统 99% 会走到这里

(2)备用:ping
if [ -x "$(command -v ping)" ]; then ping -c $(( $1 + 1 )) 127.0.0.1 > /dev/null exit 0 fi

原理:

  • ping默认每秒发一次

  • N+1个包 ≈ 等N

⚠️ 注意点:

  • 依赖ping没被禁用(某些容器里 ping 被移除或需要 CAP_NET_RAW)

  • 时间精度不如 sleep

  • 127.0.0.1保证不会阻塞网络

(3)再退一步:read -t(仅 Bash)
if [ -n "$BASH_VERSION" ]; then if command -v read >/dev/null 2>&1; then ... fi fi

原理

  • read -t N阻塞 N 秒等待输入

  • 没输入就超时返回

为什么检查-t 0 / 1 / 2

if [ -t 0 ]; then read -t "$1" -u 0; fi
  • read -t必须绑定到 TTY

  • stdin/stdout/stderr 只要有一个是 TTY 就能用

  • 非交互 shell(CI、cron)通常都不是 TTY

(4)最终兜底:busy wait(CPU 自旋)
SECONDS=0 while [[ $SECONDS -lt $1 ]]; do : done
  • SECONDS是 Bash 内建变量(秒级)

  • :是 no-op

  • 100% 占用一个 CPU 核心

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 21:14:39

GoScan终极指南:如何快速掌握交互式网络扫描利器

在网络安全的战场上,一款优秀的扫描工具往往能决定成败。GoScan作为一款革命性的交互式网络扫描器,以其独特的自动化流程和智能补全功能,正在重新定义网络扫描的标准操作。 【免费下载链接】goscan Interactive Network Scanner 项目地址: …

作者头像 李华
网站建设 2026/9/21 23:16:22

深入理解 Java 线程池:原理、应用与最佳实践

前言 在 Java 并发编程领域,线程池是一个绕不开的核心技术点。无论是高并发的互联网应用,还是后台服务系统,线程池都扮演着至关重要的角色。它不仅能够有效管理线程资源,避免线程频繁创建与销毁带来的性能开销,还能对…

作者头像 李华
网站建设 2026/9/22 1:16:04

Home Assistant OS 系统更新失败终极解决方案指南

Home Assistant OS 系统更新失败终极解决方案指南 【免费下载链接】operating-system :beginner: Home Assistant Operating System 项目地址: https://gitcode.com/gh_mirrors/op/operating-system Home Assistant Operating System(简称 HAOS)是…

作者头像 李华
网站建设 2026/9/22 0:43:26

构建工业级ReAct智能体系统:LangGraph+MCP供应链管理全栈实现!

简介 本文介绍了一个基于ReAct模式的工业级供应链管理智能体系统,采用LangGraph工作流编排和MCP工具协议。系统支持本地化部署(SQLiteOllama),提供CLI和React双界面,采用模块化设计和高性能异步处理。核心组件包括ReA…

作者头像 李华