{"record":{"id":"6504a18f23f33185","repo":"clash-verge-rev/clash-verge-rev","slug":"core-readiness-changed-while-applying-system-proxy","errorCode":null,"errorMessage":"core readiness changed while applying system proxy","messagePattern":"core readiness changed while applying system proxy","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/core/manager/lifecycle.rs","lineNumber":412,"sourceCode":"    }\n\n    pub(crate) async fn apply_proxy_after_start(&self) -> Result<()> {\n        let expectation = ProxyRestoreExpectation::capture(\n            *self.get_running_mode(),\n            self.current_core_readiness_generation(),\n            crate::core::service::owner_monitor_generation(),\n        )\n        .ok_or_else(|| anyhow::anyhow!(\"cannot apply system proxy before core readiness\"))?;\n        proxy_control::apply().await?;\n        if !expectation.is_valid(\n            self.get_running_mode().as_ref(),\n            self.current_core_readiness_generation(),\n            crate::core::service::owner_monitor_generation(),\n        ) {\n            let clear_result = proxy_control::clear().await;\n            proxy_control::stop_guard().await;\n            clear_result?;\n            anyhow::bail!(\"core readiness changed while applying system proxy\");\n        }\n        proxy_control::refresh_guard().await?;\n        if !expectation.is_valid(\n            self.get_running_mode().as_ref(),\n            self.current_core_readiness_generation(),\n            crate::core::service::owner_monitor_generation(),\n        ) {\n            let clear_result = proxy_control::clear().await;\n            proxy_control::stop_guard().await;\n            clear_result?;\n            anyhow::bail!(\"core readiness changed while refreshing the system proxy guard\");\n        }\n        Ok(())\n    }\n\n    /// 调用者须已持有 `lifecycle_lock`。\n    async fn start_core_inner(&self) -> Result<()> {\n        // 退出中不再启动新内核。","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/clash-verge-rev/clash-verge-rev/blob/5cad0f2799e74b1105e133bcfc2a45f5c1370ef1/src-tauri/src/core/manager/lifecycle.rs#L394-L430","documentation":"Thrown by CoreManager::apply_proxy_after_start after the system proxy was applied. Before applying it captured a ProxyRestoreExpectation (running mode + core readiness generation + owner-monitor generation); after `proxy_control::apply().await` returned it re-checks that expectation. If any of the three signals changed, the just-applied proxy is rolled back (clear + stop_guard) and the function bails. It exists so the app never leaves the OS pointing at a proxy whose backing core has already moved underneath it.","triggerScenarios":"Called right after a core start/restart finishes, while `lifecycle_lock` is held. The bail fires when, during the await on `proxy_control::apply()`, one of these changed: RunningMode transitioned away (e.g. core exited or was stopped), current_core_readiness_generation() bumped (core restarted under you), or crate::core::service::owner_monitor_generation() changed (the privileged service re-established ownership).","commonSituations":"A second lifecycle operation racing the first (another restart_core / change_core / activate that did not take the same config_update lock); the service manager dying and respawning mid-apply; the core crashing immediately after the readiness probe so its generation bumps; rapid config edits triggering overlapping apply calls.","solutions":["Route every restart/config path through restart_core / restart_core_during_config_update so the config_update_in_progress guard + lifecycle_lock serialize lifecycle work; do not call start/stop directly.","Retry the operation via restart_core — the expectation is re-captured on each attempt and a stable core will pass.","Check owner-monitor / service health logs around the failure timestamp; if owner_monitor_generation changed, the privileged service restarted and the core must be handed back to it.","If it reproduces, capture running_mode + both generation values before and after apply to identify which of the three signals is moving."],"exampleFix":"// before: two callers both touching lifecycle\ncore.start_core().await?;\ncore.apply_proxy_after_start().await?; // races with a concurrent restart\n\n// after: single serialized entry point\ncore.restart_core().await?; // holds config_update flag + lifecycle_lock end-to-end","handlingStrategy":"retry","validationCode":"// Before apply, confirm no other config update is in flight and the core is stable.\nif !core_manager.try_start_config_update() {\n    return Err(anyhow::anyhow!(\"defer: a config update is already running\"));\n}\n// capture an expectation and assert it is still live right before calling apply_proxy_after_start\nlet pre = (\n    *core_manager.get_running_mode(),\n    core_manager.current_core_readiness_generation(),\n    crate::core::service::owner_monitor_generation(),\n);\nif pre.0 == RunningMode::NotRunning || pre.1.is_none() {\n    return Err(anyhow::anyhow!(\"defer: core not ready before proxy apply\"));\n}","typeGuard":null,"tryCatchPattern":"match core_manager.apply_proxy_after_start().await {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"core readiness changed while applying system proxy\") => {\n        // transient race against a concurrent lifecycle op; one retry via the serialized path\n        core_manager.restart_core().await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Always mutate core lifecycle through restart_core / restart_core_during_config_update so the config_update_in_progress flag and lifecycle_lock serialize work.","Never call apply_proxy_after_start, start_core, or stop_core directly from UI/IPC handlers.","Treat core readiness generation and owner_monitor_generation as load-bearing — log them around any proxy transition."],"tags":["lifecycle","proxy","race-condition","core-manager"],"analyzedSha":"5cad0f2799e74b1105e133bcfc2a45f5c1370ef1","analyzedAt":"2026-08-12T03:25:57.699Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}