fix(java/update): 三处模块外的一锤子 GET 也上界,失败时报出卡在哪一段(M290)
`download_bytes`(M289)修掉之后,core 里还剩三处裸 `send()`,都在「拿上游文本/JSON」的 路径上:`game/java/api.rs::fetch_latest`(Adoptium latest)、`game/java/net.rs::get_text` (四家发行版的一锤子 GET)、`update/github.rs::get_candidate_text`(更新清单)。上游接了连接 就装死时它们永不返回,与 M289 修掉的是同族形态。 改动(全部复用 M289 的构造点,没有第二套超时包装): * `download/http.rs` 新增 `text_bounded`(正文按 `read_step` 的空闲判定读成 `String`,与 `bytes.rs` 的读循环同口径)与 `default_deadlines()`(取 `EngineConfig::default()` 的两段界, 不另抄秒数);`send_bounded` 的签名逐字节未动,`transfer.rs`/`chunked.rs`/`bytes.rs` 三处 调用方零改动。 * 三处一律 `send_bounded`(等响应头)+ `text_bounded`(等字节):失败时 `message` 里带 `StallReport` 的阶段(`await_headers` / `await_bytes`)与已收/预期/无进展三个数。 这三条路径只用到这两档:没有分块回收、也没有引擎许可等待,所以 `await_chunk_join` / `acquire_permit` 不适用。 * `update/github.rs` 的界取 `engine.config().deadlines()`(手里有引擎就不抄默认值)。 正常路径零改变:`fetch_latest` / `get_text` 的公开签名与错误分类一字未动(`fetch_latest` 的 404 文案与 hint 保留),`update` 的 `github_cases` 全部断言未改。 离线用例(`game::java::net::tests`,6 条;夹具 `net::probe::Hanging` 也是 M290 新增): silent 服务端 ⇒ 引擎自己在 200ms 上界内返回(外层 5s 护栏没触发),并断言 message 含 「等响应头/await_headers」;正文停住 ⇒ 「等字节/await_bytes」;200 + 真 JSON ⇒ 正文逐字节 原样(含中文)。变异已实跑:`send_bounded` 退回裸 `send()` 三处各自在外层 5s 处挂断; `text_bounded` 退回 `response.text()` ⇒ 等字节用例 5s 挂断;正文改成空串 ⇒ 正常路径用例红。 门禁:cargo check(core+cli, --all-targets)/ test --lib(892 passed)/ --test game(120)/ --test launch(91)/ --test engine_mock(17)/ clippy -D warnings / fmt --check,退出码全 0。
This commit is contained in:
1 parent
53de2a3922
commit
ad99303a95
4 files changed
+481
-59
No files matched your search
@@ -54,8 +54,10 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::download::cache::now_ms;
|
||||
use crate::download::stall::Deadlines;
|
||||
use crate::download::{
|
||||
Candidate, DownloadEngine, DownloadError, DownloadErrorKind, DownloadRequest, ResourceKind,
|
||||
http, Candidate, DownloadEngine, DownloadError, DownloadErrorKind, DownloadRequest,
|
||||
ResourceKind,
|
||||
};
|
||||
|
||||
use super::channel::Channel;
|
||||
@@ -227,41 +229,61 @@ impl GitHubReleaseSource {
|
||||
}
|
||||
|
||||
/// 单个候选地址取文本;`Ok(None)` = 404/410(该候选没有这份文件)。
|
||||
///
|
||||
/// 界取**引擎自己的** `deadlines()`:本源手里就有引擎,不另抄秒数(与 M289 给
|
||||
/// `download_bytes` 的口径同源)。
|
||||
fn get_candidate_text(&self, url: &str) -> Result<Option<String>, UpdateError> {
|
||||
let client = self.engine.client();
|
||||
crate::download::block_on(async {
|
||||
let response = client.get(url).send().await.map_err(|error| {
|
||||
UpdateError::new(
|
||||
UpdateErrorKind::Network,
|
||||
format!("取更新清单失败:{error}({url})"),
|
||||
)
|
||||
})?;
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::NOT_FOUND || status == reqwest::StatusCode::GONE {
|
||||
return Ok(None);
|
||||
}
|
||||
if !status.is_success() {
|
||||
return Err(UpdateError::new(
|
||||
UpdateErrorKind::Network,
|
||||
format!("更新清单返回 HTTP {status}({url})"),
|
||||
));
|
||||
}
|
||||
let text = response.text().await.map_err(|error| {
|
||||
UpdateError::new(
|
||||
UpdateErrorKind::Network,
|
||||
format!("读更新清单响应失败:{error}({url})"),
|
||||
)
|
||||
})?;
|
||||
if text.trim().is_empty() {
|
||||
return Err(UpdateError::invalid_manifest(format!(
|
||||
"清单响应为空({url}),发布脚本可能漏传了 {MANIFEST_ASSET}"
|
||||
)));
|
||||
}
|
||||
Ok(Some(text))
|
||||
})
|
||||
let deadlines = self.engine.config().deadlines();
|
||||
crate::download::block_on(candidate_text(self.engine.client(), url, deadlines))
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个候选地址的整段文本(`Ok(None)` = 404/410):**两处 `await` 都有界**(M290)。
|
||||
///
|
||||
/// 等响应头走 [`http::send_bounded`]、正文走 [`http::text_bounded`],与引擎里
|
||||
/// `download_bytes` 同一套构造点;失败时 `message` 里带**阶段**(等响应头 / 等字节)+ 三个数,
|
||||
/// 不再是「取更新清单失败」一句话。
|
||||
///
|
||||
/// 为什么是自由函数、`deadlines` 是参数:这条路的公开方法要 `&self`(引擎在 `Arc` 里),而
|
||||
/// 它内部是 `block_on` 的同步签名——同步路径塞进 tokio 用例就会嵌套 `block_on`(panic),
|
||||
/// 所以离线用例只能走到这一层;界从外面给,用例才压得到毫秒级。
|
||||
/// `pub(crate)` 也只是为了那条用例(它住在 `game::java::net` 的 tests 里,那里才有行数余量)。
|
||||
pub(crate) async fn candidate_text(
|
||||
client: &reqwest::Client,
|
||||
url: &str,
|
||||
deadlines: Deadlines,
|
||||
) -> Result<Option<String>, UpdateError> {
|
||||
let response = http::send_bounded(client.get(url), url, deadlines, None)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
UpdateError::new(UpdateErrorKind::Network, format!("取更新清单失败:{error}"))
|
||||
})?;
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::NOT_FOUND || status == reqwest::StatusCode::GONE {
|
||||
return Ok(None);
|
||||
}
|
||||
if !status.is_success() {
|
||||
return Err(UpdateError::new(
|
||||
UpdateErrorKind::Network,
|
||||
format!("更新清单返回 HTTP {status}({url})"),
|
||||
));
|
||||
}
|
||||
let text = http::text_bounded(response, url, deadlines)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
UpdateError::new(
|
||||
UpdateErrorKind::Network,
|
||||
format!("读更新清单响应失败:{error}"),
|
||||
)
|
||||
})?;
|
||||
if text.trim().is_empty() {
|
||||
return Err(UpdateError::invalid_manifest(format!(
|
||||
"清单响应为空({url}),发布脚本可能漏传了 {MANIFEST_ASSET}"
|
||||
)));
|
||||
}
|
||||
Ok(Some(text))
|
||||
}
|
||||
|
||||
impl UpdateSource for GitHubReleaseSource {
|
||||
fn source_id(&self) -> &str {
|
||||
&self.id
|
||||
|
||||
Reference in new issue
Block a user