portal
portal/config/settings.yml
provider_models
对上游 POST /v1/images/generations 发送与 chorus 当前完全一致的请求体,不设客户端上限,跑到底:
POST /v1/images/generations
http_code=200 ttfb=161.97s total=385.10s size=4,877,772 B avg=12.7 KB/s
传输曲线(每 30 秒累计字节):
一次完整生成分两段:生成阶段 162 秒无任何响应字节,随后传输阶段 223 秒以 17~25 KB/s 稳定吐出 4.65 MB base64,全程无停顿。
chorus 的 http.Client.Timeout 是从发起请求到读完 body 的绝对总时长(internal/platform/http/safehttp.go:94),body 由 Do() 用 io.ReadAll 一次读完,整个读取过程都在该上限内。当前配置 300 秒,在收到 3.24 MB(完整响应的 66%)时被切断,只差 145 秒、1.4 MB。
http.Client.Timeout
internal/platform/http/safehttp.go:94
Do()
io.ReadAll
上游没有故障,也不是"对复杂提示词无响应"。ResponseHeaderTimeout 从未起作用。
ResponseHeaderTimeout
size
size:"1024x1024"
extra_body
retryable
timeout_ms
三处必须联动,缺一不可:
provider.http_timeout_seconds
provider_models.id=4
gpt-image-2
context.WithTimeout
worker.lease_seconds
900 秒对实测 385 秒有 2.3 倍余量。
启动校验会拒绝不一致的组合:
// internal/config/config.go:220 if cfg.WorkerLeaseDuration <= cfg.ProviderHTTPTimeout+5*time.Second { /* 拒绝启动 */ }
internal/config/file.go:84 对 YAML 有同样约束。只改其中一到两处,结果要么被静默截断,要么进程起不来。
internal/config/file.go:84
现在 lease_seconds=360 小于实测的 385 秒。即使放开 HTTP 超时,租约也会在请求完成前到期,任务可能被另一个 worker 重新取走,造成对上游的重复调用。
lease_seconds=360
现有校验 lease > http_timeout + 5 只比对配置值,管不到真实耗时,因此这个洞不会被它发现。本次把 lease 提到 960 一并覆盖。
lease > http_timeout + 5
极端情况下单个任务会占用一个 worker 槽位最长 900 秒。当前为单机单人使用,可接受。后续若提高并发,需另立工单讨论 worker 并发度。
预计修改文件:
internal/config/config.go:220
"三层超时必须联动、否则被截断或起不来",以及"上游可能在生成阶段长时间不返回字节、随后慢速流式传输",属长期排错知识,需写入 Troubleshooting。
lease_seconds > http_timeout_seconds + 5
status=succeeded
rendered_prompt
attempts
# 1. 确认配置 Get-Content portal/config/settings.yml # 2. 重启 Portal 并确认进程与端口 ./scripts/status-all.bat # 3. 浏览器登录 test 账号,提交与 generation 13 相同的提示词,仅提交一次 # 4. 核对终态 # SELECT id,status,error_code,provider_attempt_count, # JSON_EXTRACT(attempts,'$[1].latency_ms') lat_ms # FROM generations ORDER BY id DESC LIMIT 3;
配置变更(不进版本库)
portal/config/settings.yml 被 .gitignore:49 排除,timeout_ms 是数据库配置数据,两者均无提交记录。portal/config/settings.example.yml(已跟踪)按计划保持 45 / 60 未改动。
.gitignore:49
portal/config/settings.example.yml
timeout_ms 通过 SQL 修改(本会话无法驱动管理端页面),影响行数 1:
UPDATE provider_models SET timeout_ms=900000 WHERE id=4 AND model_id='gpt-image-2' AND api_type='images';
已确认 config_audit_logs 表在当前库中不存在,此举未绕过任何已实现的审计机制。
config_audit_logs
代码变更
378a95e docs: 补充上游慢速流式传输与三层超时判读 (#62)
docs/06-troubleshooting.md
服务重启
D:\supervisor\supervisord.exe ctl /c D:\supervisor\supervisord.conf restart chorus-user chorus-user: restarted
新进程 PID 67652,启动 22:52:01,日志末行 chorus portal listening on 127.0.0.1:8085,无配置错误,即 internal/config/config.go:220 的 lease > timeout + 5 校验通过。
chorus portal listening on 127.0.0.1:8085
lease > timeout + 5
真实生成验证 —— generation 14,人工在浏览器用 test 账号提交一次,提示词与 generation 13 相同:
test
provider_attempt_count
error_code
retryable: false
latency_ms: 465462
image/png
按旧配置该任务会在 22:59:44(300 秒)被切断;实际撑到 23:02:30 完成,确认 900 秒上限生效。provider_attempt_count=1 确认 960 秒租约未提前到期,本工单要修的租约不足问题已消除。
provider_attempt_count=1
耗时与实测基线对照:受控请求 385 秒 / 4.65 MB,本次 465 秒 / 3.45 MB,同一量级。465 秒占 900 秒预算的 52%。
流程验证
python dev_scripts/harness.py sync --check → Wiki 镜像检查通过(15 个页面全部一致) python dev_scripts/harness.py check --strict → DevHarness 检查通过 python -m unittest discover -s tests → OK
未运行 go build/vet/test:本工单不含 Go 代码改动。
go build/vet/test
attempts.latency_ms
id=3
id=5
378a95e
配置类改动无提交哈希,证据以本节记录的配置内容、SQL 语句和运行结果为准。
Troubleshooting
22f9502779d019edbf433d632905eddcb41f648b
修改内容:把"attempt latency 接近超时值"的判读从"先检查配置"扩展为明确说明不能据此判断上游无响应,补充 http.Client.Timeout 覆盖整个 body 读取的语义、三层确认顺序、"不要靠反复上调超时试错"的约束,以及本次实测基线(生成阶段约 160 秒无数据,17–25 KB/s 传输,总时长 385–465 秒,响应体 3.4–4.9 MB)。
generation_outputs.width
height
openai_provider_model.txt
.git/info/exclude
settings.yml
timeout_ms=300000
把 http.Client.Timeout 的绝对总时长语义改为响应头超时 + body 读取空闲间隔超时 + 宽松绝对兜底,与 cmhub 行为一致。
依据:实测显示生成阶段有 162 秒完全无数据,传输阶段速率稳定且无停顿,因此空闲阈值需大于 162 秒(建议 240~300 秒),这样无论响应体多大都不会误杀,也不必反复上调绝对值。
涉及 internal/platform/http/safehttp.go 与配置校验,改动面大,不与本工单合并。
internal/platform/http/safehttp.go
验收标准第一项要查的是 "请求超时"(等待上游返回),不是连接超时:
net.Dialer.Timeout = 10s
internal/platform/http/safehttp.go:64
只比数字可能不够。若 cmhub 用 Python requests,其 timeout=(connect, read) 中的 read 是两次数据到达之间的最大间隔,而 chorus 的 http.Client.Timeout 是整个请求的绝对总时长。两者数值相同,行为也不同:上游持续缓慢返回数据时 cmhub 不会超时,chorus 到点必砍。
requests
timeout=(connect, read)
因此确认 cmhub 默认值时,一并确认它是间隔超时还是总时长超时。如果是间隔超时,则不能直接把该秒数抄成 chorus 的 http_timeout_seconds,需要按"该模型实际最长生成时长"另行取值。
http_timeout_seconds
本条为待核实假设,未读过 cmhub 源码(本机无该仓库)。
阶段:待实施 → 进行中。
前置 #58 检查:其配置基线(300 / 300000 / 360)已部署并在运行中,本工单在此基线上继续调整同一组值,真实依赖已满足。#58 本身仍为待验收,不受本工单影响。
工作区检查:config/local-services.yml 有既存改动、admin/config/settings.yml 与一张截图未跟踪,均为用户已有内容,与本任务无关,保持原样不提交。
config/local-services.yml
admin/config/settings.yml
provider_models.timeout_ms
执行的 SQL(影响行数 1):
Portal 已通过 Supervisor 重启:
新进程 PID 67652,启动时间 22:52:01,日志末行 chorus portal listening on 127.0.0.1:8085,无配置错误——说明 lease_seconds > http_timeout_seconds + 5 的启动校验(internal/config/config.go:220)已通过。
portal/config/settings.example.yml(已跟踪)按计划保持 45 / 60 不变。
验收标准要求"提交一次真实生成并核对终态",但本会话无法完成提交:
api_keys
chorus-runtime.env.ps1
CHORUS_API_KEY
CHORUS_API_KEY_RATE_LIMIT_*
因此请人工在浏览器中用 test 账号提交一次与 generation 13 相同的提示词,其余核对与证据回写由 Agent 完成。在此之前本工单保持进行中,不标记待验收。
用户于 2026-08-26 明确验收通过。
验收依据:generation 14 成功(465 秒、provider_attempt_count=1、输出 3,448,452 字节 PNG 与缩略图齐全),配置三处值为 900 / 900000 / 960,sync --check、check --strict 与单元测试均通过。详见工单正文"完成证据"。
sync --check
check --strict
提交 378a95e 已推送至 origin/main。
origin/main
本工单关闭。工单正文"未验证部分"记录的四项(900 秒余量的长期充分性、管理端页面修改 timeout_ms 的路径、其他 ProviderModel 的超时、并发场景)仍然有效,不因验收而消除。
正文"遗留"一节提出的空闲间隔超时治本方案尚未建单,需要时另行提出。
No dependencies set.
The note is not visible to the blocked user.
基本信息
依赖与并行
子项目影响
portal(运行配置)、管理端中的 ProviderModel 配置数据portal/config/settings.yml与provider_models表原始需求
要解决什么
实测证据(2026-08-25,经用户授权的受控请求)
对上游
POST /v1/images/generations发送与 chorus 当前完全一致的请求体,不设客户端上限,跑到底:传输曲线(每 30 秒累计字节):
一次完整生成分两段:生成阶段 162 秒无任何响应字节,随后传输阶段 223 秒以 17~25 KB/s 稳定吐出 4.65 MB base64,全程无停顿。
根因
chorus 的
http.Client.Timeout是从发起请求到读完 body 的绝对总时长(internal/platform/http/safehttp.go:94),body 由Do()用io.ReadAll一次读完,整个读取过程都在该上限内。当前配置 300 秒,在收到 3.24 MB(完整响应的 66%)时被切断,只差 145 秒、1.4 MB。上游没有故障,也不是"对复杂提示词无响应"。
ResponseHeaderTimeout从未起作用。被证伪的两个假设
size参数导致图过大":错。对照请求显示,加与不加size:"1024x1024",上游返回的都是 1536×1024,该中转站直接忽略size。补extra_body无效。做什么 / 不做什么
retryable判定、SSRF 校验、端口白名单或数据库结构。provider_models中除timeout_ms以外的字段,不再尝试补size。已确认方案
三处必须联动,缺一不可:
portal/config/settings.yml→provider.http_timeout_secondstimeout_ms不可能超过它provider_models.id=4(gpt-image-2)的timeout_ms,管理端修改context.WithTimeoutportal/config/settings.yml→worker.lease_seconds900 秒对实测 385 秒有 2.3 倍余量。
启动校验会拒绝不一致的组合:
internal/config/file.go:84对 YAML 有同样约束。只改其中一到两处,结果要么被静默截断,要么进程起不来。同时修掉的租约不足问题
现在
lease_seconds=360小于实测的 385 秒。即使放开 HTTP 超时,租约也会在请求完成前到期,任务可能被另一个 worker 重新取走,造成对上游的重复调用。现有校验
lease > http_timeout + 5只比对配置值,管不到真实耗时,因此这个洞不会被它发现。本次把 lease 提到 960 一并覆盖。已知代价
极端情况下单个任务会占用一个 worker 槽位最长 900 秒。当前为单机单人使用,可接受。后续若提高并发,需另立工单讨论 worker 并发度。
预计修改文件:
portal/config/settings.ymlgpt-image-2(provider_models.id=4)的timeout_ms,属配置数据不属代码需求变化记录
worker.lease_seconds不足的修复设计与原型门禁
internal/platform/http/safehttp.go:94、internal/config/config.go:220文档影响
"三层超时必须联动、否则被截断或起不来",以及"上游可能在生成阶段长时间不返回字节、随后慢速流式传输",属长期排错知识,需写入 Troubleshooting。
交付文档影响
验收标准
lease_seconds > http_timeout_seconds + 5。status=succeeded,有受保护输出与缩略图,rendered_prompt非空。attempts中记录的耗时与实测量级(约 400 秒)相符,且未出现第二次 provider 尝试(证明租约未提前到期导致重复调用)。验证方式
完成证据
最终差异
配置变更(不进版本库)
portal/config/settings.yml→provider.http_timeout_secondsportal/config/settings.yml→worker.lease_secondsprovider_models.id=4→timeout_msportal/config/settings.yml被.gitignore:49排除,timeout_ms是数据库配置数据,两者均无提交记录。portal/config/settings.example.yml(已跟踪)按计划保持 45 / 60 未改动。timeout_ms通过 SQL 修改(本会话无法驱动管理端页面),影响行数 1:已确认
config_audit_logs表在当前库中不存在,此举未绕过任何已实现的审计机制。代码变更
378a95e docs: 补充上游慢速流式传输与三层超时判读 (#62)—— 仅docs/06-troubleshooting.md一个文件,是 Wiki 镜像的导出结果。服务重启
新进程 PID 67652,启动 22:52:01,日志末行
chorus portal listening on 127.0.0.1:8085,无配置错误,即internal/config/config.go:220的lease > timeout + 5校验通过。测试结果
真实生成验证 —— generation 14,人工在浏览器用
test账号提交一次,提示词与 generation 13 相同:provider_attempt_counterror_coderetryable: false,无 error_code,latency_ms: 465462image/png,3,448,452 字节,已落盘按旧配置该任务会在 22:59:44(300 秒)被切断;实际撑到 23:02:30 完成,确认 900 秒上限生效。
provider_attempt_count=1确认 960 秒租约未提前到期,本工单要修的租约不足问题已消除。耗时与实测基线对照:受控请求 385 秒 / 4.65 MB,本次 465 秒 / 3.45 MB,同一量级。465 秒占 900 秒预算的 52%。
流程验证
未运行
go build/vet/test:本工单不含 Go 代码改动。未验证部分
attempts.latency_ms与基线,而非直接上调超时。timeout_ms经管理端页面修改的路径未验证:本次走 SQL,管理端表单对该字段的校验与生效行为未覆盖。id=3(文本,60000)、id=5(图片编辑,300000)保持原值,其超时是否充足未验证。提交哈希
378a95e(Wiki 镜像)配置类改动无提交哈希,证据以本节记录的配置内容、SQL 语句和运行结果为准。
长期 Wiki 页面与 revision
Troubleshooting→22f9502779d019edbf433d632905eddcb41f648b修改内容:把"attempt latency 接近超时值"的判读从"先检查配置"扩展为明确说明不能据此判断上游无响应,补充
http.Client.Timeout覆盖整个 body 读取的语义、三层确认顺序、"不要靠反复上调超时试错"的约束,以及本次实测基线(生成阶段约 160 秒无数据,17–25 KB/s 传输,总时长 385–465 秒,响应体 3.4–4.9 MB)。实施中发现、未纳入本工单的问题
generation_outputs.width与height对 generation 14 均为 NULL,但该图实际为 1536×1024,输出尺寸未落库。不影响本次验收,未处理。openai_provider_model.txt(仓库根目录,.git/info/exclude排除)中的 api key 已失效,且为明文。建议删除。风险和回退
settings.yml恢复 300/360,ProviderModel 恢复timeout_ms=300000,重启 Portal;不删除已产生的 generation 与 attempt 审计记录。遗留(需另建工单)
把
http.Client.Timeout的绝对总时长语义改为响应头超时 + body 读取空闲间隔超时 + 宽松绝对兜底,与 cmhub 行为一致。依据:实测显示生成阶段有 162 秒完全无数据,传输阶段速率稳定且无停顿,因此空闲阈值需大于 162 秒(建议 240~300 秒),这样无论响应体多大都不会误杀,也不必反复上调绝对值。
涉及
internal/platform/http/safehttp.go与配置校验,改动面大,不与本工单合并。待确认项澄清(2026-08-25)
验收标准第一项要查的是 "请求超时"(等待上游返回),不是连接超时:
net.Dialer.Timeout = 10s(internal/platform/http/safehttp.go:64),两边都已单独隔离,不是本工单的调整对象。provider.http_timeout_seconds。实施时需要额外核对的语义差异
只比数字可能不够。若 cmhub 用 Python
requests,其timeout=(connect, read)中的 read 是两次数据到达之间的最大间隔,而 chorus 的http.Client.Timeout是整个请求的绝对总时长。两者数值相同,行为也不同:上游持续缓慢返回数据时 cmhub 不会超时,chorus 到点必砍。因此确认 cmhub 默认值时,一并确认它是间隔超时还是总时长超时。如果是间隔超时,则不能直接把该秒数抄成 chorus 的
http_timeout_seconds,需要按"该模型实际最长生成时长"另行取值。本条为待核实假设,未读过 cmhub 源码(本机无该仓库)。
fix: 放宽图片生成整体超时并联动三层配置to fix: 按实测数据放宽图片生成超时并修复租约不足开始实施(2026-08-25)
阶段:待实施 → 进行中。
前置 #58 检查:其配置基线(300 / 300000 / 360)已部署并在运行中,本工单在此基线上继续调整同一组值,真实依赖已满足。#58 本身仍为待验收,不受本工单影响。
工作区检查:
config/local-services.yml有既存改动、admin/config/settings.yml与一张截图未跟踪,均为用户已有内容,与本任务无关,保持原样不提交。实施中发现的偏差(需在验收时确认)
portal/config/settings.yml被.gitignore:49排除,是本机运行配置;provider_models.timeout_ms是数据库配置数据。两者都不进版本库,因此"提交哈希"一栏将填"无代码提交",证据以配置内容和运行结果为准。timeout_ms通过 SQL 直接修改,而非管理端页面(本会话无法驱动浏览器)。已确认config_audit_logs表在当前库中不存在,因此此举没有绕过任何已实现的审计机制。执行的语句会原文记录在完成证据中。portal/config/settings.example.yml(已跟踪)不修改,保持 45 / 60 的保守默认值。它是新装模板,不应被本机实测值覆盖。配置已生效,等待人工完成验证提交(2026-08-25)
已完成
portal/config/settings.yml→provider.http_timeout_seconds.gitignore:49排除,不进版本库)portal/config/settings.yml→worker.lease_secondsprovider_models.id=4的timeout_ms执行的 SQL(影响行数 1):
Portal 已通过 Supervisor 重启:
新进程 PID 67652,启动时间 22:52:01,日志末行
chorus portal listening on 127.0.0.1:8085,无配置错误——说明lease_seconds > http_timeout_seconds + 5的启动校验(internal/config/config.go:220)已通过。portal/config/settings.example.yml(已跟踪)按计划保持 45 / 60 不变。阻塞:最后一步需要人工操作
验收标准要求"提交一次真实生成并核对终态",但本会话无法完成提交:
test账号;api_keys只有 1 条且只存哈希;chorus-runtime.env.ps1中并没有CHORUS_API_KEY变量(此前误读,那两行实际是CHORUS_API_KEY_RATE_LIMIT_*)。因此请人工在浏览器中用
test账号提交一次与 generation 13 相同的提示词,其余核对与证据回写由 Agent 完成。在此之前本工单保持进行中,不标记待验收。待验证的预期
provider_attempt_count应为 1,attempts中只有一次 provider 尝试——若出现第二次,说明租约仍然不足,需要重新评估 960 秒。验收通过(2026-08-26)
用户于 2026-08-26 明确验收通过。
验收依据:generation 14 成功(465 秒、
provider_attempt_count=1、输出 3,448,452 字节 PNG 与缩略图齐全),配置三处值为 900 / 900000 / 960,sync --check、check --strict与单元测试均通过。详见工单正文"完成证据"。提交
378a95e已推送至origin/main。本工单关闭。工单正文"未验证部分"记录的四项(900 秒余量的长期充分性、管理端页面修改
timeout_ms的路径、其他 ProviderModel 的超时、并发场景)仍然有效,不因验收而消除。正文"遗留"一节提出的空闲间隔超时治本方案尚未建单,需要时另行提出。