修复 inline 路径修复性回读缺少绝对 deadline
Project CI / Repository checks (pull_request) Successful in 1m6s
Project CI / Native shell tests (pull_request) Successful in 2m28s
Project CI / Backend tests (pull_request) Successful in 3m30s
Project CI / Frontend tests (pull_request) Successful in 1m14s

上一轮把修复性回读绑到外层 request_deadline,但 with_external_call_deadline
只在队列 worker 与 openai_image_generation 两处调用,inline HTTP 请求的
RequestContext 默认 external_call_deadline=None,于是 download_deadline 传下去
仍是 None,走无界 download.await——该修复实际只对队列模式生效。

守卫改为在 request_deadline 缺失时自行以 Instant::now() +
EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION 重新计时派生上界,两种模式下都不再
出现无界 GET。结构断言同时要求出现 unwrap_or_else 兜底与该常量。

同时修正决策记录里「最多增加一次 OSS GET」的措辞:约束是不重复读取已成功
取得的对象,而非整个请求至多一次 GET;第一次回读失败或被像素预算掐断时
允许第二次也是最后一次尝试,不视为违规。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 13:12:21 +00:00
parent 9a37deeaf0
commit bc28f00c2d
2 changed files with 23 additions and 6 deletions
@@ -24,7 +24,9 @@
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 的角色与图标像素规整降级路径;不改变成功路径、OSS PUT 次数、资源类型、画布项或前端契约,OSS GET 仍只在尺寸漂移时发生。
- 验证方式:`pixel_art_degrade_paths_guard_postprocessed_delivery_dimensions` 结构断言固定"降级分支不得返回 `(postprocessed, None, …)`"与守卫的委托实现;运行 `cargo test -p api-server editor_project --manifest-path server-rs/Cargo.toml``npm run check:rustfmt``npm run check:encoding``git diff --check`
- 关联文档:本文件「2026-07-29 角色带背景原图与透明图统一交付尺寸」与「2026-07-28 图片生成风格使用可扩展字段并以纯内存像素规整首发」。
- 补充(同日):守卫的回读必须分两类处理,否则会把「最多增加一次 OSS GET」放大成两次、且第二次无界。已取得 provider 原图的四条降级分支(permit 获取失败、worker 内 deadline、join 异常、worker 超时)改走纯内存守卫 `degrade_editor_pixel_art_with_provider_source`,零额外 GET;尚未取得原图的三条分支(进函数即预算耗尽、第一次回读失败、第一次回读超时)才走会回读的守卫,且该次 GET 以**外层请求 deadline**(而非已耗尽的像素预算)为绝对上界——用像素预算绑会让修复必然失败,不绑则突破预算。`apply_editor_postprocessed_alpha_from_persisted_provider_source_or_original` 因此新增可选 `download_deadline`,非像素路径传 `None` 保持既有语义不变。计数断言固定「回读守卫 3 处、内存守卫 4 处」,防止后续新增分支时误用回读版本。
- 补充(同日):守卫的回读必须分两类处理。已取得 provider 原图的四条降级分支(permit 获取失败、worker 内 deadline、join 异常、worker 超时)改走纯内存守卫 `degrade_editor_pixel_art_with_provider_source`,零额外 GET;尚未取得原图的三条分支(进函数即预算耗尽、第一次回读失败、第一次回读超时)才走会回读的守卫。计数断言固定「回读守卫 3 处、内存守卫 4 处」,防止后续新增分支时误用回读版本。
- OSS 回读口径(修正此前「最多增加一次 OSS GET」的措辞):约束是**不重复读取已经成功取得的对象**,而不是"整个请求至多一次 GET"。仅在尺寸漂移且尚未持有原图时才发起最多一次修复性回读,失败后不再重试;因此第一次回读失败或被像素预算掐断时,允许存在第二次、也是最后一次尝试——第一次超时往往并非 OSS 异常,而是被 30 秒像素预算切断,此时对象通常可正常读取,放弃修复反而会让角色更频繁地退化为原图单产物。
- 回读上界:修复性回读必须始终有绝对 deadline。优先取外层 `request_deadline`,但它只在队列 worker 路径上有值——inline HTTP 请求的 `RequestContext` 默认 `external_call_deadline = None`,此时守卫自行以 `Instant::now() + EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION` 重新计时派生上界,不得退化为无界 `download.await``apply_editor_postprocessed_alpha_from_persisted_provider_source_or_original` 的可选 `download_deadline` 只对像素守卫传值,非像素路径继续传 `None` 保持既有语义不变。结构断言固定守卫内必须同时出现 `request_deadline.unwrap_or_else(``EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION`,防止兜底上界被移除后静默退回无界。
---
@@ -3263,8 +3263,16 @@ async fn degrade_editor_pixel_art_to_postprocessed_with_dimension_guard(
reason: String,
) -> (DownloadedOpenAiImage, Option<AppError>, Option<String>) {
// 中文注释:只有尚未成功取得 provider 原图的降级分支才会走到这里。像素预算此时
// 多半已经耗尽,不能拿它去绑这次回读(否则必然失败、守卫形同虚设),改用外层
// 请求 deadline 作为绝对上界,保证这唯一一次 GET 有界。
// 多半已经耗尽,不能拿它去绑这次回读(否则必然失败、守卫形同虚设)。优先用外层
// 请求 deadline,但它只在队列 worker 路径上有值——inline HTTP 请求的
// RequestContext 默认没有 external_call_deadline,此时必须自行重新计时派生一个
// 上界,绝不允许出现无界 GET。
let repair_deadline = request_deadline.unwrap_or_else(|| {
let started_at = Instant::now();
started_at
.checked_add(EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION)
.unwrap_or(started_at)
});
let (image, dimension_error) =
apply_editor_postprocessed_alpha_from_persisted_provider_source_or_original(
state,
@@ -3272,7 +3280,7 @@ async fn degrade_editor_pixel_art_to_postprocessed_with_dimension_guard(
delivery_width,
delivery_height,
postprocessed,
request_deadline,
Some(repair_deadline),
)
.await;
(image, dimension_error, Some(reason))
@@ -9423,12 +9431,19 @@ mod tests {
"async fn apply_editor_postprocessed_alpha_and_pixel_art_from_persisted_provider_source_or_original",
&["download_editor_persisted_image_object"],
);
// 会回读原图的守卫必须带绝对 deadline 参数,不允许无界 GET。
// 会回读原图的守卫必须带绝对 deadline,且在外层 request_deadline 缺失时
// inline HTTP 请求的 RequestContext 默认就没有)必须自行派生兜底上界,
// 不允许退化成无界 GET。
assert_function_contains_in_order(
source,
"async fn degrade_editor_pixel_art_to_postprocessed_with_dimension_guard",
"fn degrade_editor_pixel_art_with_provider_source",
&["request_deadline: Option<Instant>", "request_deadline,"],
&[
"request_deadline: Option<Instant>",
"request_deadline.unwrap_or_else(",
"EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION",
"Some(repair_deadline),",
],
);
// 守卫必须复用非像素路径的同一套尺寸比对与 alpha 回贴,保证尺寸一致时
// 不产生额外 OSS GET,漂移修不好时返回尺寸错误。