收紧编辑器图片持久化的上传边界
OSS 共享客户端扩展为读写共用并按上传方向放宽整体超时 画板生成图片持久化的 PUT 与 HEAD 改走共享客户端 补齐持久化写路径的共享客户端回归断言 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8614,14 +8614,18 @@ async fn persist_editor_generated_image_data(
|
||||
}))
|
||||
})?;
|
||||
let persisted_mime_type = prepared.format.mime_type.clone();
|
||||
let http_client = reqwest::Client::new();
|
||||
// 中文注释:写路径此前也是每次新建 client,PUT 与 HEAD 都没有超时——请求可以在
|
||||
// 上传阶段无限期挂住,而这一段发生在 CPU 处理之后,任何按处理预算派生的 deadline
|
||||
// 都已经不适用(余额多半为零,传进来只会把算完的结果丢掉)。这里的界只能来自
|
||||
// 客户端级超时,与读路径共用同一个进程级客户端。
|
||||
let http_client = state.editor_oss_http_client();
|
||||
let put_result = oss_client
|
||||
.put_object(&http_client, prepared.request)
|
||||
.put_object(http_client, prepared.request)
|
||||
.await
|
||||
.map_err(|error| map_oss_error(error, "aliyun-oss"))?;
|
||||
let head = oss_client
|
||||
.head_object(
|
||||
&http_client,
|
||||
http_client,
|
||||
OssHeadObjectRequest {
|
||||
object_key: put_result.object_key.clone(),
|
||||
},
|
||||
@@ -9025,7 +9029,7 @@ async fn read_editor_reference_image_object(
|
||||
// 中文注释:共享客户端自带 connect / total 超时,GET 不再可能无界挂起;
|
||||
// 顺带复用连接池,避免每次读参考图都重新做一次 TLS 握手。
|
||||
let mut response = state
|
||||
.editor_oss_read_http_client()
|
||||
.editor_oss_http_client()
|
||||
.get(signed.signed_url.as_str())
|
||||
.send()
|
||||
.await
|
||||
@@ -14050,7 +14054,7 @@ mod tests {
|
||||
"bytes.extend_from_slice",
|
||||
// 中文注释:必须走进程级共享客户端。它自带 connect / total 超时,
|
||||
// 是所有 OSS 读取调用方(含没有 deadline 可传的降级路径)的兜底上界。
|
||||
".editor_oss_read_http_client()",
|
||||
".editor_oss_http_client()",
|
||||
],
|
||||
);
|
||||
assert_function_not_contains(
|
||||
@@ -14064,6 +14068,21 @@ mod tests {
|
||||
"reqwest::Client::new()",
|
||||
],
|
||||
);
|
||||
// 中文注释:写路径与读路径同一条不变式。persist 发生在 CPU 处理之后,处理预算
|
||||
// 已经不适用,客户端级超时是它唯一的界;退回裸客户端会让 PUT / HEAD 重新无界,
|
||||
// 而这一段是全部 9 条编辑器图片持久化流程共用的。
|
||||
assert_function_contains(
|
||||
source,
|
||||
"async fn persist_editor_generated_image_data",
|
||||
"async fn persist_editor_provider_source_image",
|
||||
&["state.editor_oss_http_client()"],
|
||||
);
|
||||
assert_function_not_contains(
|
||||
source,
|
||||
"async fn persist_editor_generated_image_data",
|
||||
"async fn persist_editor_provider_source_image",
|
||||
&["reqwest::Client::new()"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -272,7 +272,7 @@ pub struct AppStateInner {
|
||||
bgfilter_image_validation_limiter: Arc<Semaphore>,
|
||||
character_animation_oss_http_client: reqwest::Client,
|
||||
character_animation_oss_io_limiter: Arc<Semaphore>,
|
||||
editor_oss_read_http_client: reqwest::Client,
|
||||
editor_oss_http_client: reqwest::Client,
|
||||
#[cfg(any())]
|
||||
creative_agent_executor: Arc<MockLangChainRustAgentExecutor>,
|
||||
// Phase 1 任务 E 的 creative session facade 暂存在 api-server。
|
||||
@@ -531,7 +531,7 @@ impl AppState {
|
||||
let character_animation_oss_http_client = build_character_animation_oss_http_client()?;
|
||||
let character_animation_oss_io_limiter =
|
||||
Arc::new(Semaphore::new(CHARACTER_ANIMATION_OSS_MAX_CONCURRENCY));
|
||||
let editor_oss_read_http_client = build_editor_oss_read_http_client()?;
|
||||
let editor_oss_http_client = build_editor_oss_http_client()?;
|
||||
let http_request_permit_pools = HttpRequestPermitPools::from_config(&config);
|
||||
let (profile_recharge_order_updates, _) = broadcast::channel(128);
|
||||
|
||||
@@ -579,7 +579,7 @@ impl AppState {
|
||||
bgfilter_image_validation_limiter,
|
||||
character_animation_oss_http_client,
|
||||
character_animation_oss_io_limiter,
|
||||
editor_oss_read_http_client,
|
||||
editor_oss_http_client,
|
||||
#[cfg(any())]
|
||||
creative_agent_executor: Arc::new(MockLangChainRustAgentExecutor),
|
||||
#[cfg(any())]
|
||||
@@ -1320,8 +1320,8 @@ impl AppState {
|
||||
self.character_animation_oss_io_limiter.clone()
|
||||
}
|
||||
|
||||
pub fn editor_oss_read_http_client(&self) -> &reqwest::Client {
|
||||
&self.editor_oss_read_http_client
|
||||
pub fn editor_oss_http_client(&self) -> &reqwest::Client {
|
||||
&self.editor_oss_http_client
|
||||
}
|
||||
|
||||
#[cfg(any())]
|
||||
@@ -2096,23 +2096,27 @@ fn build_character_animation_oss_http_client() -> Result<reqwest::Client, AppSta
|
||||
})
|
||||
}
|
||||
|
||||
// 中文注释:编辑器参考图 / 派生图的 OSS GET 此前每次调用都 `reqwest::Client::new()`,
|
||||
// 既没有任何超时也没有连接复用——一个半开或黑洞的连接可以无限期挂住,同时占着
|
||||
// HTTP 准入许可和已经读入的最多 32 MiB 图片缓冲。这里收口成进程级共享客户端,
|
||||
// 给整段 GET(含 body 流式读取)一个绝对上界,作为所有调用方的兜底。
|
||||
// 中文注释:编辑器图片的 OSS 读写此前每次调用都 `reqwest::Client::new()`,既没有任何
|
||||
// 超时也没有连接复用——一个半开或黑洞的连接可以无限期挂住,同时占着 HTTP 准入许可和
|
||||
// 已经读入的最多 32 MiB 图片缓冲。这里收口成进程级共享客户端,给整段请求(含 body
|
||||
// 流式读写)一个绝对上界,作为所有调用方的兜底。读写共用一个客户端,两个方向都受同一份
|
||||
// EDITOR_REFERENCE_IMAGE_MAX_SIZE_BYTES 约束,合用也能让 GET / PUT / HEAD 复用连接池。
|
||||
//
|
||||
// connect 取 10s:同区域 OSS 建连是百毫秒级,30s 只会让不可达端点多占 20s 槽位。
|
||||
// total 取 60s:需要容纳 EDITOR_REFERENCE_IMAGE_MAX_SIZE_BYTES 上限对象在慢链路上读完。
|
||||
fn build_editor_oss_read_http_client() -> Result<reqwest::Client, AppStateInitError> {
|
||||
// total 取 120s:按较慢的写方向定尺寸——32 MiB 上传在 60s 内要求持续约 4.4 Mbps,
|
||||
// 留一倍余量避免误伤正常流量。读方向不因此变松:完美像素的 GET 另受 30s 处理预算约束,
|
||||
// 客户端超时只是兜底。
|
||||
fn build_editor_oss_http_client() -> Result<reqwest::Client, AppStateInitError> {
|
||||
reqwest::Client::builder()
|
||||
.connect_timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
.timeout(std::time::Duration::from_secs(120))
|
||||
.pool_idle_timeout(std::time::Duration::from_secs(300))
|
||||
.pool_max_idle_per_host(8)
|
||||
.tcp_keepalive(std::time::Duration::from_secs(60))
|
||||
.build()
|
||||
.map_err(|error| {
|
||||
AppStateInitError::DependencyUnavailable(format!(
|
||||
"初始化编辑器 OSS 读取 HTTP 客户端失败:{error}"
|
||||
"初始化编辑器 OSS HTTP 客户端失败:{error}"
|
||||
))
|
||||
})
|
||||
}
|
||||
@@ -2265,12 +2269,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_state_reuses_editor_oss_read_client() {
|
||||
fn app_state_reuses_editor_oss_client() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
|
||||
assert!(std::ptr::eq(
|
||||
state.editor_oss_read_http_client(),
|
||||
state.editor_oss_read_http_client(),
|
||||
state.editor_oss_http_client(),
|
||||
state.editor_oss_http_client(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user