difyctl SSE read failures become unhandled rejections while the consumer processes an event

当 difyctl 的 SSE 消费端正在处理上一个已产出事件、尚未继续调用迭代器时,后台读取 Pump 因响应体失败而提前拒绝,就会触发 difyctl SSE read failures become unhandled rejections while the consumer process

快速结论:当 difyctl 的 SSE 消费端正在处理上一个已产出事件、尚未继续调用迭代器时,后台读取 Pump 因响应体失败而提前拒绝,就会触发 difyctl SSE read failures become unhandled rejections while the consumer processes an event,Node 默认策略会直接终止进程。优先排查 cli/src/call/sse.ts 中 Pump 的创建与 await pump 的绑定时机。

适用环境:Dify 源码 main 分支提交 0838e2511da0ef0953b59d90d5407e0b441e2b32,difyctl 1.17.1;自托管源码构建;复现环境为 Windows、Node.js 24.18.0,仅做隔离的 CLI 流复现,未部署服务端、未调用模型;依赖 eventsource-parser@4.1.1。

最快修复方案:在创建 pump 后立即挂一个拒绝观察者,例如 pump.catch(() => {}),保留原有 await pump 以把原始错误继续传给消费端。Issue 评论中给出了该写法,属于“可优先尝试”的修复思路,未经合并验证。

注意事项:该修复不增加重试、不改变 SSE 事件顺序;只消除“无处理器导致的未处理拒绝”,错误仍会通过迭代器抛给消费端。若消费端以 return() 或 break 提前退出后 pump 才失败,也属于同一类未处理拒绝,需一并覆盖。

问题场景

在 Dify 源码仓库的 CLI 目录下运行 difyctl 相关调用链,使用 cli/src/call/sse.ts 中的 parseSSE 消费 SSE 流。当消费端从异步生成器拿到一个事件、正在写入或处理该事件时,响应体读取失败(例如连接断开),进程在消费端的 catch 执行之前就退出。

报错原文

difyctl SSE read failures become unhandled rejections while the consumer processes an event

first event:
Error: network disconnected
    at .../repro.mjs:...
Node.js v24.18.0

原因分析

可能原因:pump 异步函数立即启动,但其 Promise 只在队列排空且 done 为 true 后通过 await pump 被等待。如果消费端停在 yield ev 处处理事件,此时 reader.read() 失败,pump 会先于任何错误处理器拒绝;Node 默认的未处理拒绝策略在消费端处理流错误之前就终止进程。另一类同样场景是消费端通过 return() 或 break 提前退出后 pump 才失败,此时 finally 块不会等待 pump,拒绝同样无人处理。

环境排查

  • 确认 Dify 源码版本是否为 0838e2511da0ef0953b59d90d5407e0b441e2b32,difyctl 是否为 1.17.1。
  • 确认 Node.js 版本,复现环境为 24.18.0。
  • 确认 eventsource-parser 版本是否为仓库锁定的 4.1.1。
  • 确认 CLI 依赖已安装,且在仓库根目录运行复现脚本。
  • 确认是否启用了 --unhandled-rejections=strict,该参数会让复现稳定以退出码 1 结束。

解决步骤

  1. 打开 cli/src/call/sse.ts,定位 pump 异步函数的创建位置。
  2. 在 pump 创建后立即附加一个空操作的拒绝观察者,例如 pump.catch(() => {})。该调用返回新 Promise,原 pump 仍保持拒绝状态,原有 await pump 逻辑不受影响。
  3. 保留 done 分支中的 await pump 以及 finally 块的现有逻辑,确保原始错误继续向消费端传播。
  4. 补充回归用例:先产出一个事件,在消费端挂起期间调用 controller.error(...),等待数毫秒后断言下一次 events.next() 以该错误拒绝。
  5. 回归用例需对未处理拒绝敏感:可利用 Vitest 默认行为,或在测试中注册 process.on('unhandledRejection') 监听并断言其从未被调用。

验证方法

使用 Issue 提供的 cli/repro.mjs,在仓库根目录执行 node --unhandled-rejections=strict cli/repro.mjs。修复前进程在打印 caught by consumer 之前以状态码 1 退出;修复后应正常输出 first event 与 caught by consumer: network disconnected,且不出现未处理拒绝。同时确认回归测试在未修复状态下失败、修复后通过。

参考来源

langgenius/dify #43942

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29342

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注