mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-06 00:32:13 +08:00
fix(service): stop waiting on a service that no longer starts with Windows
When the service's start type was changed from automatic, startup waited about two minutes for it before falling back to Sidecar. Detect this immediately, skip the TUN readiness wait, and offer a reinstall that restores automatic start. Refs #8129
This commit is contained in:
@@ -823,7 +823,12 @@ impl CoreManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if crate::core::runstate::RUN_STATE.state().service_usable() {
|
||||
let state = crate::core::runstate::RUN_STATE.state();
|
||||
// Waiting cannot bring up a service nothing will start.
|
||||
if state.service_usable()
|
||||
|| matches!(&state.health, crate::core::runstate::ServiceHealth::Unavailable(reason)
|
||||
if crate::core::service::is_not_auto_started(reason))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@ impl RunStateEnv for RealEnv {
|
||||
async fn probe_service_version(&self) -> Result<ServiceVersionReply> {
|
||||
// The IPC client would otherwise back off for about two minutes before failing.
|
||||
#[cfg(windows)]
|
||||
if tokio::task::spawn_blocking(crate::core::service::service_stopped)
|
||||
if let Some(reason) = tokio::task::spawn_blocking(crate::core::service::service_stop_reason)
|
||||
.await
|
||||
.context("service status probe did not finish")??
|
||||
{
|
||||
anyhow::bail!("the Windows service is not running");
|
||||
anyhow::bail!(reason);
|
||||
}
|
||||
let response = clash_verge_service_ipc::get_version().await?;
|
||||
let core = if response.code == 0
|
||||
|
||||
@@ -49,6 +49,15 @@ static PENDING_SERVICE_OWNER_NOTICE: Mutex<Option<String>> = Mutex::new(None);
|
||||
pub enum ServiceFallbackNotice {
|
||||
Unavailable,
|
||||
CoreRejected(String),
|
||||
NotAutoStarted,
|
||||
}
|
||||
|
||||
/// The release installer registers AutoStart.
|
||||
const SERVICE_NOT_AUTO_STARTED: &str = "the Windows service is stopped and no longer starts with Windows";
|
||||
|
||||
pub(crate) fn is_not_auto_started(reason: &str) -> bool {
|
||||
// Detection prefixes its own context to the reason.
|
||||
reason.contains(SERVICE_NOT_AUTO_STARTED)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -61,6 +70,8 @@ pub(crate) fn take_service_fallback_notice() -> Option<ServiceFallbackNotice> {
|
||||
let reason = PENDING_SERVICE_FALLBACK_NOTICE.lock().take()?;
|
||||
Some(if reason.starts_with(CORE_REJECTED_PREFIX) {
|
||||
ServiceFallbackNotice::CoreRejected(reason)
|
||||
} else if is_not_auto_started(&reason) {
|
||||
ServiceFallbackNotice::NotAutoStarted
|
||||
} else {
|
||||
ServiceFallbackNotice::Unavailable
|
||||
})
|
||||
@@ -266,7 +277,7 @@ fn open_registered_service() -> Result<Option<windows_service::service::Service>
|
||||
let manager = WindowsServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
|
||||
match manager.open_service(
|
||||
clash_verge_service_ipc::WINDOWS_SERVICE_NAME,
|
||||
ServiceAccess::QUERY_STATUS,
|
||||
ServiceAccess::QUERY_STATUS | ServiceAccess::QUERY_CONFIG,
|
||||
) {
|
||||
Ok(service) => Ok(Some(service)),
|
||||
Err(WindowsServiceError::Winapi(error)) if error.raw_os_error() == Some(ERROR_SERVICE_DOES_NOT_EXIST) => {
|
||||
@@ -281,21 +292,34 @@ pub(crate) fn trusted_service_evidence() -> Result<bool> {
|
||||
Ok(open_registered_service()?.is_some())
|
||||
}
|
||||
|
||||
/// Whether IPC cannot succeed until the service is started again. A service that is starting, or
|
||||
/// that the SCM has not started yet this boot, is left to the IPC retries, which wait for it.
|
||||
/// Why IPC cannot succeed until the service is started again, if it cannot. A starting service,
|
||||
/// or an AutoStart one the SCM has yet to start this boot, is left to the IPC retries.
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn service_stopped() -> Result<bool> {
|
||||
use windows_service::service::{ServiceExitCode, ServiceState};
|
||||
pub(crate) fn service_stop_reason() -> Result<Option<&'static str>> {
|
||||
use windows_service::service::{ServiceExitCode, ServiceStartType, ServiceState};
|
||||
|
||||
const ERROR_SERVICE_NEVER_STARTED: u32 = 1077;
|
||||
const NOT_RUNNING: &str = "the Windows service is not running";
|
||||
let Some(service) = open_registered_service()? else {
|
||||
return Ok(true);
|
||||
return Ok(Some(NOT_RUNNING));
|
||||
};
|
||||
let status = service
|
||||
.query_status()
|
||||
.context("failed to query Windows service status")?;
|
||||
Ok(status.current_state == ServiceState::Stopped
|
||||
&& status.exit_code != ServiceExitCode::Win32(ERROR_SERVICE_NEVER_STARTED))
|
||||
if status.current_state != ServiceState::Stopped {
|
||||
return Ok(None);
|
||||
}
|
||||
// The development channel registers an on-demand start.
|
||||
if !cfg!(feature = "verge-dev")
|
||||
&& service
|
||||
.query_config()
|
||||
.context("failed to query Windows service configuration")?
|
||||
.start_type
|
||||
!= ServiceStartType::AutoStart
|
||||
{
|
||||
return Ok(Some(SERVICE_NOT_AUTO_STARTED));
|
||||
}
|
||||
Ok((status.exit_code != ServiceExitCode::Win32(ERROR_SERVICE_NEVER_STARTED)).then_some(NOT_RUNNING))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
Reference in New Issue
Block a user