fix(upload): 测速时单条线路失败不再中断整体测速 (#1674)

Probe::probe 对每条线路的 ping 使用 ? 传播错误,任意一条线路
DNS/连接/读取失败都会让整个测速立即返回错误,即使后续线路
完全可用,调用方也会因 unwrap_or_default 直接退回默认线路。

现在单条线路网络错误或异常状态码只记录警告并跳过该线路,
继续测速其余候选;全部线路失败时返回统一错误。
This commit is contained in:
Noah authored and GitHub committed 2026-08-30 14:51:09 +00:00
1 parent 616432d190
commit 5c6c720fdc
1 file changed
+84 -12
+84 -12
View File
@@ -13,7 +13,7 @@ use crate::error::Kind::{Custom, RateLimit};
use crate::uploader::bilibili::{BiliBili, Video};
use crate::uploader::line::upos::{Upos, UposPart};
use std::time::Instant;
use tracing::info;
use tracing::{info, warn};
pub mod upos;
@@ -87,22 +87,44 @@ impl Probe {
.await?
.json()
.await?;
// let client = res.ping(client);
Self::select_line(client, &res.probe, res.lines).await
}
/// 逐条线路测速并选出耗时最短者。
/// 单条线路网络错误或异常状态码只跳过该线路,全部失败才返回错误。
async fn select_line(
client: &reqwest::Client,
probe: &serde_json::Value,
lines: Vec<Line>,
) -> Result<Line> {
let mut choice_line: Line = Default::default();
for mut line in res.lines {
for mut line in lines {
let instant = Instant::now();
if Probe::ping(&res.probe, &format!("https:{}", line.probe_url), client)
match Probe::ping(probe, &format!("https:{}", line.probe_url), client)
.send()
.await?
.status()
.is_success()
.await
{
line.cost = instant.elapsed().as_millis();
info!("{}: {}", line.query, line.cost);
if choice_line.cost > line.cost {
choice_line = line
Ok(response) if response.status().is_success() => {
line.cost = instant.elapsed().as_millis();
info!("{}: {}", line.query, line.cost);
if choice_line.cost > line.cost {
choice_line = line
}
}
};
Ok(response) => {
warn!(
"{} 测速返回异常状态码 {},跳过该线路",
line.query,
response.status()
);
}
Err(e) => {
warn!("{} 测速失败,跳过该线路: {e}", line.query);
}
}
}
if choice_line.cost == u128::MAX {
return Err(Custom("所有上传线路测速均失败".to_string()));
}
Ok(choice_line)
}
@@ -498,3 +520,53 @@ pub fn akbd() -> Line {
cost: 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn broken_line(query: &str) -> Line {
Line {
os: Uploader::Upos,
// "https:" 拼接后不是合法 URL,send() 直接报错且不产生网络请求
probe_url: String::new(),
query: query.into(),
cost: 0,
}
}
/// 单条线路 send 失败不应中断测速,全部失败时返回统一错误而不是首条线路的网络错误。
#[tokio::test]
async fn select_line_skips_failed_lines_and_reports_all_failed() {
let client = reqwest::Client::new();
let result = Probe::select_line(
&client,
&serde_json::json!({"get": {}}),
vec![broken_line("a"), broken_line("b")],
)
.await;
match result {
Err(Custom(message)) => assert_eq!(message, "所有上传线路测速均失败"),
other => panic!("期望所有线路失败的统一错误,实际为 {other:?}"),
}
}
/// 无候选线路时同样返回错误,而不是把未测速的默认线路当作结果。
#[tokio::test]
async fn select_line_rejects_empty_lines() {
let client = reqwest::Client::new();
let result = Probe::select_line(&client, &serde_json::json!({"get": {}}), Vec::new()).await;
assert!(result.is_err());
}
/// 真实网络测速仍能选出可用线路。默认忽略,本地验证:
/// `cargo test -p biliup probe_selects_line_over_network -- --ignored --nocapture`
#[tokio::test]
#[ignore = "requires network access to member.bilibili.com"]
async fn probe_selects_line_over_network() {
let client = reqwest::Client::new();
let line = Probe::probe(&client).await.expect("测速应选出可用线路");
assert_ne!(line.cost, u128::MAX);
println!("selected line: {} cost={}ms", line.query, line.cost);
}
}