mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-09 18:59:36 +00:00
fix(downloads): allow aria2 graceful shutdown delay
This commit is contained in:
+11
-19
@@ -3131,7 +3131,10 @@ async fn shutdown_aria2_daemon(app_handle: tauri::AppHandle) {
|
||||
let child = guard.child.lock().ok().and_then(|mut child| child.take());
|
||||
if let Some(mut child) = child {
|
||||
let _ = tokio::task::spawn_blocking(move || {
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(3);
|
||||
// Aria2 1.37.0 intentionally schedules an RPC-requested shutdown
|
||||
// three seconds after replying. Leave a bounded margin for that
|
||||
// timer and the subsequent state flush before forcing termination.
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(8);
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_)) => return,
|
||||
@@ -3148,11 +3151,6 @@ async fn shutdown_aria2_daemon(app_handle: tauri::AppHandle) {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
if let Some(state) = app_handle.try_state::<AppState>() {
|
||||
if let Err(error) = state.storage_layout.promote_aria2_server_stat_output() {
|
||||
log::warn!("adaptive mirror history could not be promoted: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Aria2DaemonGuard {
|
||||
@@ -9306,15 +9304,14 @@ fn apply_aria2_torrent_dht_options(
|
||||
|
||||
fn apply_aria2_server_stat_options(
|
||||
command: &mut std::process::Command,
|
||||
input_path: Option<&std::path::Path>,
|
||||
output_path: Option<&std::path::Path>,
|
||||
path: Option<&std::path::Path>,
|
||||
) {
|
||||
let (Some(input_path), Some(output_path)) = (input_path, output_path) else {
|
||||
let Some(path) = path else {
|
||||
return;
|
||||
};
|
||||
command
|
||||
.arg(format!("--server-stat-if={}", input_path.display()))
|
||||
.arg(format!("--server-stat-of={}", output_path.display()))
|
||||
.arg(format!("--server-stat-if={}", path.display()))
|
||||
.arg(format!("--server-stat-of={}", path.display()))
|
||||
.arg("--server-stat-timeout=86400");
|
||||
}
|
||||
|
||||
@@ -11097,9 +11094,8 @@ mod tests {
|
||||
fn aria2_adaptive_mirror_history_is_private_and_launch_scoped() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let path = root.path().join("server-stat.txt");
|
||||
let output_path = root.path().join("server-stat.next");
|
||||
let mut command = std::process::Command::new("aria2c");
|
||||
apply_aria2_server_stat_options(&mut command, Some(&path), Some(&output_path));
|
||||
apply_aria2_server_stat_options(&mut command, Some(&path));
|
||||
assert_eq!(
|
||||
command
|
||||
.get_args()
|
||||
@@ -11107,13 +11103,13 @@ mod tests {
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
format!("--server-stat-if={}", path.display()),
|
||||
format!("--server-stat-of={}", output_path.display()),
|
||||
format!("--server-stat-of={}", path.display()),
|
||||
"--server-stat-timeout=86400".to_string(),
|
||||
]
|
||||
);
|
||||
|
||||
let mut disabled = std::process::Command::new("aria2c");
|
||||
apply_aria2_server_stat_options(&mut disabled, None, None);
|
||||
apply_aria2_server_stat_options(&mut disabled, None);
|
||||
assert_eq!(disabled.get_args().count(), 0);
|
||||
}
|
||||
|
||||
@@ -13891,9 +13887,6 @@ pub fn run() {
|
||||
None
|
||||
}
|
||||
};
|
||||
let aria2_server_stat_output_path = aria2_server_stat_path
|
||||
.as_ref()
|
||||
.map(|_| storage_layout.aria2_server_stat_output_path());
|
||||
if let Err(error) = crate::torrent::remove_orphaned_probe_dirs(app.handle()) {
|
||||
log::warn!("could not remove orphaned torrent probes: {error}");
|
||||
}
|
||||
@@ -14177,7 +14170,6 @@ pub fn run() {
|
||||
apply_aria2_server_stat_options(
|
||||
&mut cmd,
|
||||
aria2_server_stat_path.as_deref(),
|
||||
aria2_server_stat_output_path.as_deref(),
|
||||
);
|
||||
|
||||
apply_aria2_torrent_peer_discovery_options(
|
||||
|
||||
@@ -9,7 +9,6 @@ const ARIA2_DATA_DIR: &str = "aria2";
|
||||
const ARIA2_DHT_FILE: &str = "dht.dat";
|
||||
const ARIA2_DHT6_FILE: &str = "dht6.dat";
|
||||
const ARIA2_SERVER_STAT_FILE: &str = "server-stat.txt";
|
||||
const ARIA2_SERVER_STAT_OUTPUT_FILE: &str = "server-stat.next";
|
||||
const MAX_ARIA2_SERVER_STAT_BYTES: u64 = 1024 * 1024;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -125,12 +124,6 @@ impl StorageLayout {
|
||||
.join(ARIA2_SERVER_STAT_FILE)
|
||||
}
|
||||
|
||||
pub fn aria2_server_stat_output_path(&self) -> PathBuf {
|
||||
self.data_dir
|
||||
.join(ARIA2_DATA_DIR)
|
||||
.join(ARIA2_SERVER_STAT_OUTPUT_FILE)
|
||||
}
|
||||
|
||||
/// Create and validate only Firelink's Aria2 state directory. Aria2 owns
|
||||
/// the table contents; Firelink owns this exact location and must never
|
||||
/// fall back to a user-global default when it cannot establish it.
|
||||
@@ -233,100 +226,8 @@ impl StorageLayout {
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
|
||||
.map_err(|error| format!("failed to protect Aria2 server-stat cache: {error}"))?;
|
||||
}
|
||||
let output_path = self.aria2_server_stat_output_path();
|
||||
match std::fs::symlink_metadata(&output_path) {
|
||||
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
|
||||
return Err("Aria2 server-stat session output is not a regular file".to_string());
|
||||
}
|
||||
Ok(_) => std::fs::remove_file(&output_path).map_err(|error| {
|
||||
format!("failed to reset Aria2 server-stat session output: {error}")
|
||||
})?,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => {
|
||||
return Err(format!(
|
||||
"failed to inspect Aria2 server-stat session output: {error}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Promote Aria2's session-only output after the daemon has exited. Keeping
|
||||
/// the input and output paths distinct avoids the locked MSVC engine's
|
||||
/// inability to replace an existing destination with C `rename`.
|
||||
pub fn promote_aria2_server_stat_output(&self) -> Result<(), String> {
|
||||
let path = self.aria2_server_stat_path();
|
||||
let output_path = self.aria2_server_stat_output_path();
|
||||
let directory = self.data_dir.join(ARIA2_DATA_DIR);
|
||||
if crate::path_has_symlink_component(&directory) {
|
||||
return Err("Aria2 server-stat directory contains a symlink".to_string());
|
||||
}
|
||||
|
||||
let output_metadata = match std::fs::symlink_metadata(&output_path) {
|
||||
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
|
||||
return Err("Aria2 server-stat session output is not a regular file".to_string());
|
||||
}
|
||||
Ok(metadata) => metadata,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
|
||||
Err(error) => {
|
||||
return Err(format!(
|
||||
"failed to inspect Aria2 server-stat session output: {error}"
|
||||
));
|
||||
}
|
||||
};
|
||||
let output = if output_metadata.len() <= MAX_ARIA2_SERVER_STAT_BYTES {
|
||||
std::fs::read_to_string(&output_path).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if !output
|
||||
.as_deref()
|
||||
.is_some_and(aria2_server_stat_is_valid)
|
||||
{
|
||||
std::fs::remove_file(&output_path).map_err(|error| {
|
||||
format!("failed to discard invalid Aria2 server-stat session output: {error}")
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
|
||||
return Err(
|
||||
"Aria2 server-stat cache replacement target is not a regular file".to_string(),
|
||||
);
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) => {
|
||||
return Err(format!(
|
||||
"failed to inspect Aria2 server-stat replacement target: {error}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&path)
|
||||
.and_then(|mut file| {
|
||||
use std::io::Write;
|
||||
file.write_all(output.as_deref().unwrap_or_default().as_bytes())?;
|
||||
file.sync_all()
|
||||
})
|
||||
.map_err(|error| {
|
||||
format!("failed to promote Aria2 server-stat session output: {error}")
|
||||
})?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
|
||||
.map_err(|error| format!("failed to protect Aria2 server-stat cache: {error}"))?;
|
||||
}
|
||||
std::fs::remove_file(&output_path)
|
||||
.map_err(|error| format!("failed to remove Aria2 server-stat session output: {error}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn aria2_server_stat_is_valid(contents: &str) -> bool {
|
||||
@@ -494,34 +395,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aria2_server_stat_output_is_promoted_only_after_validation() {
|
||||
let root = TempDir::new().unwrap();
|
||||
let layout = test_layout(root.path());
|
||||
layout.prepare_aria2_dht_paths().unwrap();
|
||||
let path = layout.prepare_aria2_server_stat_path().unwrap();
|
||||
let output_path = layout.aria2_server_stat_output_path();
|
||||
fs::write(&path, "").unwrap();
|
||||
fs::write(
|
||||
&output_path,
|
||||
"host=mirror.example, protocol=https, dl_speed=1, last_updated=1, status=OK\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
layout.promote_aria2_server_stat_output().unwrap();
|
||||
assert!(fs::read_to_string(&path)
|
||||
.unwrap()
|
||||
.contains("host=mirror.example"));
|
||||
assert!(!output_path.exists());
|
||||
|
||||
fs::write(&output_path, "invalid\n").unwrap();
|
||||
layout.promote_aria2_server_stat_output().unwrap();
|
||||
assert!(fs::read_to_string(&path)
|
||||
.unwrap()
|
||||
.contains("host=mirror.example"));
|
||||
assert!(!output_path.exists());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn aria2_server_stat_cache_rejects_symlink_output() {
|
||||
@@ -538,12 +411,6 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert!(layout.prepare_aria2_server_stat_path().is_err());
|
||||
fs::write(
|
||||
layout.aria2_server_stat_output_path(),
|
||||
"host=mirror.example, protocol=https, dl_speed=1, last_updated=1, status=OK\n",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(layout.promote_aria2_server_stat_output().is_err());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
Reference in New Issue
Block a user