Skip to content

fix(proxy): retain client-abort billing under high-concurrency mode - #1452

Merged
ding113 merged 2 commits into
devfrom
fix/high-concurrency-client-abort-retention
Aug 25, 2026
Merged

fix(proxy): retain client-abort billing under high-concurrency mode#1452
ding113 merged 2 commits into
devfrom
fix/high-concurrency-client-abort-retention

Conversation

@ding113

@ding113 ding113 commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Summary

高并发模式下继续保留有界的客户端中断计费(client-abort retention):已完成的 Codex 流在客户端挂断后仍按 200 计费并保留 sticky/affinity 绑定,修复误判 499、清除 session 绑定、前缀缓存命中率下降约 40% 的回归。

原因

高并发模式把 shouldRetainClientAbortBilling() 置为 false,导致 response-handler.ts 在客户端断开时立即 cancelSource("client_detached_high_concurrency") 并丢弃已收字节。Codex 读完 response.completed 就挂电话 —— 本该走“已完成则改判 200 正常计费”的救场分支 clientAbortCompleteSuccess,却被这道 !highConcurrency 硬短路掉,全部被判 499 CLIENT_ABORTEDclearSessionBinding()。绑定被清 → 下一条请求从 affinity_hit 退回 initial_selection → 每条请求切一次渠道。熔断器虽有 !clientAborted 豁免没被拉黑,但前缀缓存命中掉 40%。

改法

  • src/app/v1/_lib/proxy/session.ts:594shouldRetainClientAbortBilling() 固定返回 true,即高并发下也保留有界的客户端中断计费(client-abort-metering 64 KiB + CLIENT_ABORT_DRAIN_RESERVATION_BYTES 3 MiB,由 DetachedStreamBudget 的 64 并发 / 64 MiB 进程级预算兜底,来自 v0.9.4 fix(proxy): bound detached stream memory without disabling replay #1439/fix(proxy): harden detached stream cleanup #1440 修复后的上界)。这样已完成流仍能凭 response.completed + billingComplete 改判 200 并保留 sticky/affinity 绑定,真正截断的流继续按 499/502 走失败路径。
  • 5 语言 i18n 同步更新:enableHighConcurrencyModeDesc / highConcurrencyModeWarning 改为“有界的客户端中断保留仍会执行”。
  • 单测:session.test.ts 同步预期;system-settings-form-upstream-error-message.test.tsx 的 toast 断言同步;新增 high-concurrency-client-abort-retention.test.ts:1 3 条回归(高并发下 200 保留绑定 / 截断不误判 200 / policy 保持)。新回归测试直接驱动真实的 ProxyResponseHandler.dispatch 链路(而非复制判级公式),覆盖 test: reproduce high-concurrency mode + Codex abort → 499 bug #1451 复现套件中 Greptile 指出的“镜像逻辑漂移”隐患。

验证

  • bun run typecheck 通过
  • bunx vitest run -t "high concurrency client-abort" 3 条用例通过
  • bunx vitest run tests/unit/proxy/session.test.ts tests/unit/settings/system-settings-form-upstream-error-message.test.tsx 39 条通过
  • 复现脚本验证:高并发下已完成 Codex 流从 499 → 200,已确认不再清 binding

风险

仅解锁一条已做 OOM 上界的子路径,不涉及 Replay / hedge / gate 的高并发开关(仍关闭)。

关联 / Related


Description enhanced by Claude AI

High-concurrency mode previously disabled client-abort retention
(shouldRetainClientAbortBilling -> false), causing the response
handler to immediately cancel upstream and discard buffered bytes on
client disconnect. Completed upstream streams that Codex already
closed after reading response.completed were then classified as 499
CLIENT_ABORTED and cleared the sticky/affinity binding, forcing the
next request off affinity_hit and churning providers per request
(40% prefix-cache hit loss).

Keep bounded retention (64 KiB metering + 3 MiB reservation capped
by DetachedStreamBudget 64/64MiB from #1439/#1440) enabled even in
high-concurrency mode so completed streams are still billed as 200
and keep the binding. Genuinely truncated streams remain failures.

Update i18n (5 locales) and the high-concurrency warning to reflect
that bounded retention stays on. Add regression tests covering
ProxySession policy and the Codex 200-vs-499 finalization.
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 672f7500-f053-4bc7-975b-248c168c4439

📥 Commits

Reviewing files that changed from the base of the PR and between 32683cf and 6e31e23.

📒 Files selected for processing (9)
  • messages/en/settings/config.json
  • messages/ja/settings/config.json
  • messages/ru/settings/config.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/app/v1/_lib/proxy/session.ts
  • tests/unit/proxy/high-concurrency-client-abort-retention.test.ts
  • tests/unit/proxy/session.test.ts
  • tests/unit/settings/system-settings-form-upstream-error-message.test.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

高并发模式下,客户端中止后的有界计费保留逻辑保持启用。已完成的上游流继续按 200 成功处理并保留会话绑定。相关回归测试和五种语言的设置文案已同步更新。

Changes

高并发客户端中止保留

Layer / File(s) Summary
客户端中止保留行为
src/app/v1/_lib/proxy/session.ts
shouldRetainClientAbortBilling() 始终返回 true。高并发模式下,已完成的上游流继续按 200 计费并保留会话绑定。
行为回归测试
tests/unit/proxy/high-concurrency-client-abort-retention.test.ts, tests/unit/proxy/session.test.ts
新增测试覆盖完成流、截断流、计费状态、失败原因和会话绑定行为,并更新高并发策略断言。
高并发模式文案
messages/en/settings/config.json, messages/ja/settings/config.json, messages/ru/settings/config.json, messages/zh-CN/settings/config.json, messages/zh-TW/settings/config.json, tests/unit/settings/system-settings-form-upstream-error-message.test.tsx
设置描述、警告和提示测试移除客户端中止保留已禁用的表述,并说明该功能仍保持启用。

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 6e31e

This localized change restores bounded client-abort billing and affinity retention in high-concurrency mode, with focused regression coverage and passing checks; no actionable merge-blocking risk remains beyond normal review.

Suggested reviewers: brisbanehuang

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. (5 skipped: 5 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了主要变更:在高并发模式下保留客户端中止计费。标题简洁、具体,并与代码和测试变更一致。
Description check ✅ Passed 描述与变更内容相关。它说明了计费、sticky/affinity 绑定、失败路径、国际化文本和回归测试的修改。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. (5 skipped: 5 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/high-concurrency-client-abort-retention

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added bug Something isn't working area:session labels Aug 25, 2026
@ding113
ding113 merged commit ad51d96 into dev Aug 25, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Aug 25, 2026
@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label Aug 25, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR. The one-line production change (session.ts:594 shouldRetainClientAbortBilling() now unconditionally returns true) was verified end-to-end: it un-stubs the client-abort metering observer, removes both immediate-cancel paths, and re-enables the clientAbortCompleteSuccess 200-reclassification in high-concurrency mode, with memory safety genuinely bounded by DetachedStreamBudget (64 concurrent / 64 MiB defaults with a 16 MiB metering reserve). The new regression tests are real — reverting the predicate locally makes the dispatch-chain test fail with expected 499 to be 200, confirming it reproduces the exact bug rather than mirroring the classification logic.

PR Size: M

  • Lines changed: 438
  • Files changed: 9

Verification performed

  • Ran the new regression suite: 3/3 pass with the fix; 2/3 fail against the reverted (buggy) predicate, proving test validity
  • Ran session.test.ts + system-settings-form-upstream-error-message.test.tsx: 36/36 pass
  • tsgo --noEmit typecheck passes clean
  • All 5 locale files are valid JSON and updated consistently; toast is wired via t("highConcurrencyModeWarning") (no hardcoded strings)
  • No stale references to the old "client-abort retention disabled in high-concurrency" behavior remain anywhere in src/, messages/, or docs
  • Comment claims in session.ts verified against actual constants (64 KiB metering cap, ~3 MiB drain reservation, budget defaults)

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Note: the now-constant predicate leaves its four negated call sites in response-handler.ts as permanently-dead branches. This is acceptable for a minimal hotfix (the comment documents the rationale), but a follow-up could inline the removal if the toggle is not expected to return.


Automated review by Claude AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:session bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant