fix(config): retain runtime after an applied core replacement

This commit is contained in:
Tunglies
2026-10-06 14:24:56 +08:00
parent f6fd3887d6
commit 25e00792de
+45 -2
View File
@@ -322,7 +322,7 @@ impl CoreManager {
self.validate_and_apply(transaction).await
}
/// Validates and applies the caller's transaction, committing only on success.
/// Commits the applied runtime even if restoring the system proxy subsequently fails.
async fn validate_and_apply(&self, transaction: DraftTransaction<'_>) -> Result<ValidationOutcome> {
let outcome = self.validate_and_apply_draft().await?;
if outcome.is_valid() {
@@ -342,7 +342,10 @@ impl CoreManager {
}
let run_path = Config::write_runtime_file(&yaml).await?;
self.apply_config(run_path).await?;
let previous = self.current_core_readiness_generation();
let result = self.apply_config(run_path).await;
self.retain_applied_runtime(&Config::runtime().await, previous, &result);
result?;
// Under the lifecycle lock, so a stop for exit waits for this write instead of outrunning it.
#[cfg(target_os = "macos")]
{
@@ -352,6 +355,21 @@ impl CoreManager {
Ok(ValidationOutcome::Valid)
}
fn retain_applied_runtime(
&self,
runtime: &clash_verge_draft::Draft<IRuntime>,
previous: Option<u64>,
result: &Result<()>,
) {
if result.is_ok()
|| self
.current_core_readiness_generation()
.is_some_and(|current| Some(current) != previous)
{
runtime.apply();
}
}
/// Applies a generated configuration through the active core owner.
async fn apply_config(&self, path: PathBuf) -> Result<()> {
if matches!(*self.get_running_mode(), RunningMode::Service) {
@@ -443,6 +461,31 @@ mod tests {
use clash_verge_service_ipc::{StageRejection, StageRuntimeOutcome};
use std::{cell::Cell, time::Duration};
#[test]
fn a_ready_replacement_keeps_the_applied_runtime_when_proxy_restore_fails() -> anyhow::Result<()> {
use crate::{config::runtime::IRuntime, core::CoreManager};
use clash_verge_draft::{Draft, DraftTransaction};
for replaced in [false, true] {
let manager = CoreManager::default();
manager.mark_core_ready();
let previous = manager.current_core_readiness_generation();
let runtime = Draft::new(IRuntime::default());
let transaction = DraftTransaction::begin(vec![&runtime])?;
runtime.edit_draft(|draft| {
draft.exists_keys.insert("replacement".into());
});
if replaced {
manager.mark_core_ready();
}
let result = Err(anyhow::anyhow!("system proxy restore failed"));
manager.retain_applied_runtime(&runtime, previous, &result);
transaction.rollback();
assert_eq!(runtime.data_arc().exists_keys.contains("replacement"), replaced);
}
Ok(())
}
const CONFIRM_WITHIN: Duration = Duration::from_secs(5);
fn staged() -> StageAttempt {