惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Blog — PlanetScale
Blog — PlanetScale
S
Security @ Cisco Blogs
G
GRAHAM CLULEY
L
LINUX DO - 热门话题
AWS News Blog
AWS News Blog
P
Privacy International News Feed
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
腾讯CDC
美团技术团队
宝玉的分享
宝玉的分享
博客园 - 聂微东
小众软件
小众软件
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
雷峰网
雷峰网
T
Threatpost
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
L
LangChain Blog
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
V
Vulnerabilities – Threatpost
NISL@THU
NISL@THU
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Engineering at Meta
Engineering at Meta
T
Troy Hunt's Blog
F
Full Disclosure
博客园_首页
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
Microsoft Security Blog
Microsoft Security Blog
S
Security Affairs
量子位
Cloudbric
Cloudbric
H
Hacker News: Front Page
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
LINUX DO - 最新话题
IT之家
IT之家
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Spread Privacy
Spread Privacy
Forbes - Security
Forbes - Security
Security Archives - TechRepublic
Security Archives - TechRepublic

est の 输入 输出和出入

iosevka字体让中文 ASCII diagram 图表对齐 为什么 Github OAuth 故意拦截 CORS gitweets改版,复刻微信「朋友圈」 MiMoCode 干完活儿发通知 写作能力和 locate cost grep vs sqlite 谁更适合微信聊天记录? 或许「数羊」真是个有效的入睡法 唯物主义「天命」论 我的 Vibe Coding 最佳实践——ADR文档 MacOS 快速插入当前时间 locate cost 基于 git 的零拷贝静态web服务器 AI和柜台费 Sutton 论 discovery Elon Musk 五步工作法 Playlet:DLNA听歌神器 免安装app播放NAS里的歌 不修改nginx接收websocket AI 硬伤 理解LLM的范式——它就是个差分机? 路径依赖 AI 流式接口的pattern FSRS核心字段 SVG 时钟 AI的自我觉醒是必然 唐宋之变 Louis Alexander 谈英语学习 Predict Next Word Instruct 的意义 Sentry 替代品 Bugsink 安装配置 浏览器通过WebGPU上做AI推理 Indent Is All You Need 语文学习和考试 失落的学习兴趣 如何跟孩子讲学习的意义 物权 Bonsai 在 M2 安装 The Porsche Diffusion 首页和404更新 无用之用——或许LLM真的还不是AGI 发明后训练的人真是天才 看好 Taalas 码奸 claw会代替员工? 如果拿 token 抵工资会发生什么? tmux enables AIs to operate servers safely chat.est.im launches at 3.14 2026 股灾? AI 弑父 Configurable Intelligence 巴别塔,Vibe Coding和Lisp之咒 Citrini的2028智力危机 中文不是唯一的意译语言 观星有感 体细胞阶级论——当繁衍权被剥夺
[AI] curl -NT. 导致100% CPU原因
est · 2026-07-08 · via est の 输入 输出和出入

有AI就是好使,搁以前自己得盯半天也看不明白。。。

问题确认

curl -NT. 在连接一个持续推送数据的 streaming 服务器时,会产生一个 紧密的 pause/unpause 循环,导致 100% CPU。

根因分析

-T.(注意是点号,不是减号)将 stdin 设为非阻塞模式,然后依赖 EAGAIN + CURL_READFUNC_PAUSE 机制来避免阻塞。核心循环发生在三个回调之间:

第一步 — 上传尝试失败src/tool_cb_rea.c):

  {
    rc = read(per->infd, buffer, sz * nmemb);
    if(rc < 0) {
      if(errno == EAGAIN) {
        errno = 0;
        config->readbusy = TRUE;
        return CURL_READFUNC_PAUSE;
      }
      /* since size_t is unsigned we cannot return negative values fine */
      rc = 0;
    }
  }

stdin 没数据 → read() 返回 EAGAIN → 设 readbusy=TRUE → 暂停上传。

第二步 — 下载数据到达立刻解除暂停src/tool_cb_wrt.c):

  if(config->readbusy) {
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
  }

streaming 服务器持续发送数据 → tool_write_cb 被不断调用 → 每次都无等待地立刻解除上传暂停

第三步 — 进度回调只有 1ms 退避src/tool_cb_rea.c):

  if(config->readbusy) {
    if(ulprev == ulnow) {
#ifndef _WIN32
      waitfd(1, per->infd);
#else
      /* sleep */
      curlx_wait_ms(1);
#endif
    }

    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
  }

tool_readbusy_cb 有 1ms 的 waitfd 退避,但被 tool_write_cb 的无等待解除抢先了。

完整的死循环(每次 curl_multi_perform 内)

lib/transfer.cCurl_sendrecv 中,处理顺序是先下载后上传

  if(k->keepon & KEEP_RECV) {
    result = sendrecv_dl(data, k);
    if(result || data->req.done)
      goto out;
  }

  /* If we still have writing to do, we check if we have a writable socket. */
  if(Curl_req_want_send(data)) {
    result = sendrecv_ul(data);
    if(result)
      goto out;
  }

所以单次迭代内:
1. sendrecv_dl → 读到 streaming 数据 → tool_write_cb → 看到 readbusy=TRUE立刻解除暂停(零等待)
2. sendrecv_ultool_read_cb → stdin EAGAIN又暂停readbusy=TRUE
3. 回到 curl_multi_poll → 下载 socket 有数据 → 立刻返回(不会 sleep)
4. 重复 1-3

lib/easy.c 的主循环:

  while(!done && !mresult) {
    int still_running = 0;

    mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);

    if(!mresult)
      mresult = curl_multi_perform(multi, &still_running);

curl_multi_poll 的 1000ms 超时形同虚设——因为下载 socket 始终可读,poll() 立刻返回。

结果:每秒跑上千次这个循环,每次包含多个系统调用(pollreadwrite),CPU 直接打满。

问题本质

stdin 没有被加入 curl_multi_poll 的 pollset。只有网络 socket 参与了 poll。所以 curl 无法高效地"同时等待 stdin 和网络数据",只能靠 tool_write_cb 里的盲目 unpause 来弥补,在 streaming 场景下退化成忙等。

用户侧解决方案

如果你不需要从 stdin 上传数据(只是想看 streaming 响应),不要用 -T.

# 正确的方式:只看 streaming 输出
curl -N https://streaming-server/events

# 或者重定向 stdin 为空
curl -NT. https://streaming-server/events < /dev/null

< /dev/null 会让 read() 返回 0(EOF)而不是 EAGAIN,不会进入 pause/unpause 循环。

如果确实需要双向 streaming(边上传 stdin 边接收),目前没有完美的用户侧 workaround。可以考虑:

# 用管道加一层缓冲,减轻空转(效果有限)
cat | curl -NT. https://streaming-server/events

源码级修复思路

如果要在源码层面修复,最直接的方案是tool_write_cb 中去掉盲目 unpause,改为只在 tool_readbusy_cb 中带退避地 unpause,并增大退避时间:

// tool_cb_wrt.c — 去掉这段,或加条件限制
// 不再从 write callback 无脑解除上传暂停
if(config->readbusy) {
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);  // 删除或改为有条件触发
}
// tool_cb_rea.c — 增大退避时间
if(config->readbusy) {
    if(ulprev == ulnow) {
        waitfd(100, per->infd);  // 从 1ms 提高到 100ms
    }
    config->readbusy = FALSE;
    curl_easy_pause(per->curl, CURLPAUSE_CONT);
}

更彻底的方案是把 stdin fd 加入 curl_multi_poll 的 extra fds,但这需要改 curl_multi_poll 的调用方式,涉及面更大。