fix(polish): 润色流式请求让取消检测和网络等待赛跑,不再悬挂到 budget 结束 - #1001
Merged
H-Chris233 merged 3 commits intoAug 26, 2026
Conversation
配置为 qwen-audio-3.0-asr-flash + deepseek-v4-flash 时,若润色阶段对话模型 接口迟迟不返回,取消操作(点击胶囊关闭按钮 / 按快捷键重新发起)会晚至 32~53 秒才生效,期间胶囊卡死在屏幕上、快捷键无响应,只能强制重启 App 才能恢复(本机日志复现,见 issue)。 根因:chat_completion_messages_streaming 的取消检测只在 SSE 循环顶部轮询 一次,真正等待网络数据的两处 await(建连/发送请求、逐帧读取)都不会被 取消打断,只能等到数据到达或 budget(首字 30s+ / 空闲 20s / 硬顶 900s) 自然超时。同一类问题在转写(ASR)阶段已在 Open-Less#798 修过,但没有推广到润色 阶段——这是一处遗留的修复范围缺口。 用同款 tokio::select! 赛跑模式补齐:新增 wait_until_cancelled helper (75ms 轮询,与 wait_for_processing_cancel 同款间隔),分别和 send_with_transient_retry、response.chunk() 赛跑,命中取消就复用既有的 "空流 → InvalidResponse" 错误路径。不改变任何超时预算数值,不改变 already_streamed 语义,不改变 dictation.rs 状态机。 Fixes Open-Less#1000
上一版新增的 cancellation_before_response_arrives_* 只覆盖了「服务端连状态行 都不回」的建连阶段,走的是 send_with_transient_retry 之前那个 select!。而 issue Open-Less#1000 日志里打出的是 "cancelled by caller after 0 deltas ... breaking SSE loop",说明 response 头已到达、代码已进入 SSE 循环,真正卡住的是 response.chunk() 那一次 await——也就是本次修复的核心那处 select!,此前没有 任何回归测试覆盖:把它还原成旧写法,原用例照样通过。 补 cancellation_mid_stream_does_not_wait_out_the_budget:服务端立刻回 200 header 让客户端进入 SSE 循环,随后长时间不发 delta,取消后必须在一个轮询 周期内返回,而不是等满 30s 首字预算。 顺带把建连阶段取消返回的 status 从编造的 200 改成 0:那条路径上一个 HTTP 响应字节都没收到,日志打成 "status 200" 会让人误以为服务端回了 200 空body。 两个用例的断言因此分别锁 status 0 / 200,互相不会冒充。
外部 review(codex)指出前两版仍漏了一处不可取消的网络等待:收到非 2xx 响应头后的 response.text()。polish_client 走的是 POLISH_CLIENT_HARD_CAP_SECS (900s)总超时,服务端只要回一个 500 头就挂住不发 body,取消完全不生效, 最坏卡死 15 分钟——症状与 issue Open-Less#1000 一致,且比原路径的 30s 首字预算严重 一个数量级。 补 cancellation_while_reading_error_body_does_not_hang:服务端回 500 头并 声明 1KB body 却一字节不发。去掉这次赛跑后该用例实测耗时 5.01s(真实场景 连接不关就是等满 900s)。 三处 select! 统一加 biased,让取消分支先于网络分支被 poll,消掉「budget 归 零与取消同时就绪时随机选分支」的竞态。 按 review 意见收紧措辞与注释:原注释称 Response 被 drop 会中断底层 TCP, 在 HTTP/2 多路复用与连接池下未必成立,改为只声明放弃这次请求的等待; wait_until_cancelled 与两个既有用例的说明各压掉约一半。
H-Chris233
approved these changes
Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
Fixes #1000。
润色阶段模型迟迟不返回时,取消要等 32~53s 才生效,期间胶囊卡死、快捷键无响应,只能强制重启 App。根因是
chat_completion_messages_streaming里等待网络的 await 都不参与取消检测,should_cancel只在 SSE 循环顶部被轮询一次。同类问题在转写阶段已由 #798 修过,没有推广到润色阶段。本 PR 用同款
tokio::select!赛跑补齐该函数里的三处网络等待,取消粒度从「最长等一个 budget」收窄到 ~75ms。修复 / 新增 / 改进
wait_until_cancelled(75ms 轮询,与wait_for_processing_cancel同间隔),分别与三处网络等待赛跑:建连send_with_transient_retry、SSE 逐帧response.chunk()、非 2xx 错误体response.text()。polish_client用POLISH_CLIENT_HARD_CAP_SECS(900s)作总超时,服务端只要回一个 500 头就挂住不发 body,取消完全不生效,最坏卡死 15 分钟——比原路径的 30s 首字预算严重一个数量级。select!均加biased,让取消分支先于网络分支被 poll,消掉「budget 归零与取消同时就绪时随机选分支」的竞态。cancellation_mid_stream_*对应 issue 日志里after 0 deltas ... breaking SSE loop的真实路径;cancellation_while_reading_error_body_*在移除赛跑后实测耗时 5.01s。dictation.rs状态机、already_streamed语义、各项超时预算数值。chat_completion_history_streaming与 Codex OAuth 流式路径存在同款写法,不在本次复现路径内,留作单独跟进。兼容
already_streamed语义与dictation.rs状态机。测试计划
cargo test --manifest-path src-tauri/Cargo.toml --lib(macOS/aarch64)1227 passed; 0 failedOk;错误体那处耗时 5.01s)