feat(m165): 下载引擎按数据根跨请求复用(运行时常驻 + 构造次数守卫)
宿主侧以前每次请求都现建一个 DownloadEngine:连接池、信号量限流、 健康度记忆、内容寻址缓存索引全部清零。现在改成按数据根共享一个实例。 - core: 新增 download::shared(进程级登记处,键 = 缓存根)—— engine_for / forget / constructions;region/health/cache/mirrors 的装配 收在这一个 build() 里(持锁构造只做本地 I/O,不联网) - core: download::blocking 改成进程内唯一常驻运行时。reqwest 的连接池绑在 驱动连接的运行时上,引擎复用必须配运行时常驻,否则复用的是个装满死连接的池 - presenter: host::engine::engine 返回 Arc<DownloadEngine>(共享实例), addon/update/versions/java/authlib 等全部调用点随之改;新增 forget 供 「重新检测地区」失效(否则进程级引擎下那句话不成立) - presenter: addon 详情页把「项目 + 版本列表」两次 block_on 收成一次 失败分类 / 镜像回退 / Retry-After 语义未改动(error.rs、http.rs、engine.rs 的 候选循环一字未动)。 守卫(都带变异实证): - shared::tests::one_engine_per_cache_root + host::engine::tests:: host_requests_share_one_engine:断言 constructions(root) == 1(不是只看结果) - shared::tests::pooled_connection_survives_across_block_on_calls:127.0.0.1 回环服务统计 accept 次数,两次 block_on 必须只拨号一次 验证:cargo test -p unlucky-core --lib(608 过)、5 个 download 集成 target(42 过)、 cargo test -p unlucky-presenter --lib(267 过)、cargo clippy --all-targets、 cargo fmt --check 全绿。真站点(Modrinth/CurseForge/BMCLAPI)未打,见报告。
This commit is contained in:
1 parent
b4e6fbf8c1
commit
46389d61fd
11 files changed
+486
-48
No files matched your search
@@ -7,6 +7,11 @@
|
||||
//! | 无自定义 header(Modrinth 的全部请求) | [`DownloadEngine::download_bytes`] | **信号量限流 + 源健康度 + 指数退避 + 分类错误**,全是 core 现成的 |
|
||||
//! | 带 header(只有 CurseForge 的 `X-API-KEY`) | `engine.client()` 直发 | 引擎的请求体没有 header 字段,只能自己发;代价是这条路径自己带一个 429/5xx 退避 |
|
||||
//!
|
||||
//! 两条路径用的是**同一个引擎**,而引擎是进程级共享的(M165:`host::engine::engine` →
|
||||
//! `core::download::shared::engine_for`,一个数据根一个)。所以 Modrinth 与 CurseForge 的请求、
|
||||
//! 以及它们与真正的文件下载,共用同一条连接池与同一套限流;`block_on` 也跑在同一个常驻运行时上,
|
||||
//! 池里的空闲连接不会随着某次调用结束而变成死连接。
|
||||
//!
|
||||
//! ## 密钥纪律
|
||||
//!
|
||||
//! 带 header 的那条路径**只把 header 值喂给请求**:不进日志、不进错误串。错误串里只有 URL
|
||||
|
||||
@@ -96,12 +96,22 @@ fn modrinth_detail(
|
||||
let core_engine = engine::engine(env);
|
||||
let transport = EngineTransport::new(&core_engine);
|
||||
let client = Modrinth::new(&transport);
|
||||
let project = block_on(client.project(&request.project)).map_err(failure_of)?;
|
||||
let versions = block_on(client.versions(&VersionQuery {
|
||||
let query = VersionQuery {
|
||||
project: request.project.clone(),
|
||||
game_version: request.game_version.clone(),
|
||||
loader: request.loader.and_then(loader_facet).map(str::to_string),
|
||||
}))
|
||||
};
|
||||
// 项目详情 + 版本列表是**两次串行 GET**,包在**一次** `block_on` 里(宿主侧少一层阻塞边界,
|
||||
// 也就少一次运行时进出——M165 登记的「单请求两次 block_on」在这里收掉)。
|
||||
//
|
||||
// 这不是嵌套 `block_on`:两段 `await` 依次跑在同一个 future 里,落在同一个常驻运行时
|
||||
// (`core::download::blocking`)与同一个共享引擎上,连接池/限流照常复用。
|
||||
// 失败语义不变:哪一步先失败就先返回,与拆成两次调用时的先后顺序一致,分类仍走 `failure_of`。
|
||||
let (project, versions) = block_on(async {
|
||||
let project = client.project(&request.project).await?;
|
||||
let versions = client.versions(&query).await?;
|
||||
Ok::<_, OnlineError>((project, versions))
|
||||
})
|
||||
.map_err(failure_of)?;
|
||||
Ok(ContentDetail {
|
||||
project: if project.id.is_empty() {
|
||||
|
||||
Reference in new issue
Block a user