fix(scheduler): harden scheduled actions and speed limits

- Persist scheduler dispatch markers and retry unacknowledged events across renderer and process restarts.
- Fence queue admission, Torrent moves, and permit activation during system actions with an explicit force path.
- Preserve Aria2 zero-limit overrides and normalize global limits across startup, queued, retry, and live paths.
- Guard scheduler lifecycle races, completion post-actions, settings bindings, and regression coverage.
This commit is contained in:
NimBold
2026-08-12 18:57:31 +03:30
parent 885e3d0100
commit e9ad226b93
22 changed files with 528 additions and 71 deletions
+27 -1
View File
@@ -271,7 +271,11 @@ pub fn preserve_scheduler_runtime_keys(
};
let mut incoming_document = decode_document(&Value::String(incoming.to_string()))?;
let incoming_state = settings_state_mut(&mut incoming_document)?;
for key in ["schedulerLastStartKey", "schedulerLastStopKey"] {
for key in [
"schedulerLastStartKey",
"schedulerTriggeredStartKey",
"schedulerLastStopKey",
] {
if let Some(value) = existing_state.get(key) {
incoming_state.insert(key.to_string(), value.clone());
}
@@ -606,6 +610,8 @@ fn validate_settings(settings: &mut PersistedSettings) {
settings.minimum_normal_download_speed_ki_b,
)
.unwrap_or_default();
settings.global_speed_limit = crate::normalize_speed_limit_for_aria2(&settings.global_speed_limit)
.unwrap_or_default();
settings.torrent_overall_upload_limit = crate::normalize_speed_limit_for_aria2(
&settings.torrent_overall_upload_limit,
)
@@ -875,6 +881,7 @@ fn default_settings() -> PersistedSettings {
scheduler_running: false,
scheduler_active_download_ids: Vec::new(),
scheduler_last_start_key: String::new(),
scheduler_triggered_start_key: None,
scheduler_last_stop_key: String::new(),
last_custom_speed_limit_ki_b: 1024,
last_custom_speed_limit_unit: "MB/s".to_string(),
@@ -940,6 +947,7 @@ mod tests {
let existing = json!({
"state": {
"schedulerLastStartKey": "2026-06-22-start",
"schedulerTriggeredStartKey": "2026-06-22-start",
"schedulerLastStopKey": "2026-06-22-stop"
},
"version": 3
@@ -948,6 +956,7 @@ mod tests {
let incoming = json!({
"state": {
"schedulerLastStartKey": "",
"schedulerTriggeredStartKey": "",
"schedulerLastStopKey": "",
"theme": "system"
},
@@ -958,6 +967,10 @@ mod tests {
let merged = preserve_scheduler_runtime_keys(Some(&existing), &incoming).unwrap();
let merged: Value = serde_json::from_str(&merged).unwrap();
assert_eq!(merged["state"]["schedulerLastStartKey"], "2026-06-22-start");
assert_eq!(
merged["state"]["schedulerTriggeredStartKey"],
"2026-06-22-start"
);
assert_eq!(merged["state"]["schedulerLastStopKey"], "2026-06-22-stop");
}
@@ -1051,6 +1064,19 @@ mod tests {
assert!(settings.torrent_overall_upload_limit.is_empty());
}
#[test]
fn normalizes_invalid_global_speed_limit_to_unlimited() {
let stored = json!({
"state": {
"globalSpeedLimit": "not-a-rate"
}
});
let settings = decode_stored_settings(&Value::String(stored.to_string())).unwrap();
assert!(settings.global_speed_limit.is_empty());
}
#[test]
fn migrates_legacy_location_settings_and_preserves_custom_overrides() {
let stored = json!({