🐛 After SIGTERM, `cloudflared tunnel run` uses 100% of one CPU core for the whole graceful-shutdown period
维护者通常 1 天内回复
评估
调研方向
issue 指向 supervisor/supervisor.go 中的忙循环,位于 Supervisor.Run 对 gracefulShutdownC 执行的 select 内。阅读该循环以及 waitForSignal 中的信号处理;initialize() 不受影响。验证 SIGTERM 后,supervisor 在排空期间会阻塞,并且在隧道连接结束或宽限期结束时仍会退出。
由索引模型根据 Issue 内容生成。
描述
Describe the bug
After SIGTERM, cloudflared tunnel run uses 100% of one CPU core for the whole graceful-shutdown period (up to --grace-period). It uses ~0% before the signal, including while it serves a long-lived request.
The cause is a busy loop in Supervisor.Run (supervisor/supervisor.go). On SIGTERM, waitForSignal calls close(graceShutdownC). The supervisor's main loop selects on that channel:
case <-s.gracefulShutdownC:
shuttingDown = true
A closed channel is always ready, so this case is selected on every iteration and the for { select { ... } } loop never blocks. The loop runs until the last tunnel connection exits or the process is killed.
To Reproduce
- Run a local origin that serves a long-lived response (e.g. an SSE endpoint that writes one event per second for 90s).
- Run
cloudflared tunnel --grace-period 100s --no-autoupdate --metrics 127.0.0.1:20241 run --token <token> - Open the long-lived request through the tunnel hostname.
kill -TERM <cloudflared pid>- Sample CPU (e.g.
/proc/<pid>/statutime+stime over 3s) or watchtop: cloudflared stays at 100% of one core until it exits. curl '127.0.0.1:20241/debug/pprof/goroutine?debug=2'shows the supervisor goroutine runnable inside theRunselect loop, while the rest are blocked.
If it's an issue with Cloudflare Tunnel:
4. Tunnel ID: f499dceb-781e-470b-9943-3c1aa7674c30
5. cloudflared config: no config file; remotely managed tunnel run with --token, --grace-period 100s, --no-autoupdate. Protocol: QUIC (default). The loop is in the supervisor, so it should not depend on the protocol.
Expected behavior
While draining, cloudflared should sit near-idle, blocked until in-flight requests finish or the grace period ends.
Environment and versions
- OS: Linux (Debian 13 trixie container, kernel 7.1.5)
- Architecture: AMD64 (x86_64)
- Version: 2026.9.3 (built 2026-09-24). The same code is on
master.
Logs and errors
CPU measured over 3s windows:
idle cpu%: 0
streaming cpu%: 0
drain1 cpu%: 100
drain2 cpu%: 100
drain3 cpu%: 101
Log at SIGTERM:
INF Initiating graceful shutdown due to signal terminated ...
INF Unregistered tunnel connection connIndex=2 event=0 ip=198.41.192.7
INF Unregistered tunnel connection connIndex=1 event=0 ip=198.41.192.57
Goroutine dump during drain (54 goroutines; only the pprof handler is running and this one is runnable):
goroutine 61 [runnable]:
context.(*cancelCtx).Done(...)
/usr/local/go/src/context/context.go:448
github.com/cloudflare/cloudflared/supervisor.(*Supervisor).Run(...)
.../cloudflared/supervisor/supervisor.go:137 +0x324
github.com/cloudflare/cloudflared/supervisor.StartTunnelDaemon(...)
.../cloudflared/supervisor/tunnel.go:104 +0x50
github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel.StartServer.func5()
.../cloudflared/cmd/cloudflared/tunnel/cmd.go:521 +0x74
Additional context
Suggested fix: set the channel to nil after the first receive. A nil channel is never ready in a select, so the loop blocks again.
case <-s.gracefulShutdownC:
shuttingDown = true
s.gracefulShutdownC = nil
initialize() also selects on gracefulShutdownC, but it returns immediately on receive, so it is not affected.
Impact: we run cloudflared as the ingress next to our app server and drain for up to 100s on each deploy. During that time cloudflared takes a full core away from the app server while the app server finishes in-flight requests.
- 主要语言
- Go
- 星标
- 16.2k
- 派生
- 1.5k
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
cloudflare/cloudflared 的其他 Issue
-
Priority: Normal Type: Bug
难度 2/5 1-3 小时 新手友好度 85/100
cloudflare/cloudflared#1777 ·
维护者通常 1 天内回复
-
🐛 --output json writes invalid JSON lines when events are logged concurrently可能已有人在做 @theolujay 于 15 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
cloudflare/cloudflared#1761 · 5 条评论 ·
维护者通常 1 天内回复
-
🐛 cfRay is missing from proxied request logs (newHTTPLogger discards the zerolog context)可能已有人在做 @pankajc46 于 17 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 82/100
cloudflare/cloudflared#1756 · 1 条评论 ·
维护者通常 1 天内回复
-
tunnel route ip show: --filter-network-is-subset-of sends the superset filter可能已有人在做 @wangyusheng1985 于 17 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 92/100
cloudflare/cloudflared#1750 ·
维护者通常 1 天内回复
-
🐛 QUIC Hijack() skips the status-written check that HTTP/2 enforces可能已有人在做 @dedsec-terminal 于 11 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
cloudflare/cloudflared#1748 · 1 条评论 ·
维护者通常 1 天内回复
查看 cloudflare/cloudflared 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
blinklabs-io/dingo#5636 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
JetBrains/youtrack-cli#130 ·
维护者通常 1 天内回复
-
kind/bug needs-triage sig/release
难度 2/5 1-3 小时 新手友好度 73/100
kubernetes/kubernetes#143392 · 1 条评论 ·
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 61/100
维护者通常 1 天内回复
-
stage-fail
难度 1/5 1 小时以内 新手友好度 72/100
siyuan-note/bazaar#2405 ·
维护者通常 1 天内回复