fix(service): harden transient IPC handling

This commit is contained in:
wonfen
2026-07-26 06:31:32 +08:00
parent df66d976a5
commit 389c5342e5
5 changed files with 42 additions and 30 deletions
+25 -5
View File
@@ -17,11 +17,15 @@ use log::{Level, LevelFilter, Record};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use crate::{ use crate::{
core::service, core::{CoreManager, manager::RunningMode, service},
singleton, singleton,
utils::dirs::{self, sidecar_log_dir}, utils::dirs::{self, sidecar_log_dir},
}; };
const fn should_sync_service_writer(running_mode: RunningMode) -> bool {
matches!(running_mode, RunningMode::Service)
}
pub struct Logger { pub struct Logger {
handle: Arc<Mutex<Option<LoggerHandle>>>, handle: Arc<Mutex<Option<LoggerHandle>>>,
sidecar_file_writer: Arc<RwLock<Option<FileLogWriter>>>, sidecar_file_writer: Arc<RwLock<Option<FileLogWriter>>>,
@@ -178,14 +182,17 @@ impl Logger {
let sidecar_writer = self.generate_sidecar_writer()?; let sidecar_writer = self.generate_sidecar_writer()?;
*self.sidecar_file_writer.write() = Some(sidecar_writer); *self.sidecar_file_writer.write() = Some(sidecar_writer);
// update service writer config // The service writer is auxiliary to the local logger. Synchronize it only
if service::SERVICE_MANAGER.is_ready_cached() { // for an active service session and do not roll back local settings on failure.
service::update_writer_by_service(&clash_verge_service_ipc::WriterConfig { if should_sync_service_writer(*CoreManager::global().get_running_mode())
&& let Err(error) = service::update_writer_by_service(&clash_verge_service_ipc::WriterConfig {
directory: String::new(), directory: String::new(),
max_log_size: log_max_size * 1024, max_log_size: log_max_size * 1024,
max_log_files: log_max_count, max_log_files: log_max_count,
}) })
.await?; .await
{
logging!(warn, Type::Service, "failed to update service writer config: {error:#}");
} }
Ok(()) Ok(())
@@ -224,3 +231,16 @@ impl Logger {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::should_sync_service_writer;
use crate::core::manager::RunningMode;
#[test]
fn service_writer_sync_requires_service_running_mode() {
assert!(should_sync_service_writer(RunningMode::Service));
assert!(!should_sync_service_writer(RunningMode::Sidecar));
assert!(!should_sync_service_writer(RunningMode::NotRunning));
}
}
+5 -14
View File
@@ -107,7 +107,6 @@ const fn classify_service_install_state(current: CurrentServiceProbe, has_instal
match current { match current {
CurrentServiceProbe::Ready => ServiceInstallState::Ready, CurrentServiceProbe::Ready => ServiceInstallState::Ready,
CurrentServiceProbe::VersionMismatch => ServiceInstallState::NeedsReinstall, CurrentServiceProbe::VersionMismatch => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Unavailable if has_install_marker => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Unavailable => ServiceInstallState::Unavailable, CurrentServiceProbe::Unavailable => ServiceInstallState::Unavailable,
CurrentServiceProbe::Missing if has_install_marker => ServiceInstallState::NeedsReinstall, CurrentServiceProbe::Missing if has_install_marker => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Missing => ServiceInstallState::NotInstalled, CurrentServiceProbe::Missing => ServiceInstallState::NotInstalled,
@@ -304,7 +303,7 @@ fn trusted_service_evidence() -> Result<bool> {
} }
async fn detect_service_install_state() -> ServiceInstallState { async fn detect_service_install_state() -> ServiceInstallState {
let markers = match trusted_service_evidence() { let has_trusted_install_marker = match trusted_service_evidence() {
Ok(exists) => exists, Ok(exists) => exists,
Err(error) => { Err(error) => {
logging!( logging!(
@@ -316,7 +315,7 @@ async fn detect_service_install_state() -> ServiceInstallState {
} }
}; };
if !markers { if !has_trusted_install_marker {
return classify_service_install_state(CurrentServiceProbe::Missing, false); return classify_service_install_state(CurrentServiceProbe::Missing, false);
} }
@@ -329,7 +328,7 @@ async fn detect_service_install_state() -> ServiceInstallState {
} }
}; };
classify_service_install_state(current, markers) classify_service_install_state(current, has_trusted_install_marker)
} }
pub struct ServiceManager { pub struct ServiceManager {
@@ -1412,7 +1411,6 @@ impl ServiceManager {
} }
} }
#[cfg(target_os = "windows")]
pub async fn confirm_ready(&self) -> Result<()> { pub async fn confirm_ready(&self) -> Result<()> {
let reply = probe_service_version_once() let reply = probe_service_version_once()
.await .await
@@ -1545,14 +1543,7 @@ impl ServiceManager {
} }
pub async fn refresh(&self) -> Result<()> { pub async fn refresh(&self) -> Result<()> {
self.run_operation(async { self.run_operation(async { self.confirm_ready().await }).await
apply_service_version_result(
self,
probe_service_version_once().await,
"failed to refresh service protocol",
)
})
.await
} }
pub async fn handle_service_status(&self, status: ServiceStatus) -> Result<()> { pub async fn handle_service_status(&self, status: ServiceStatus) -> Result<()> {
@@ -2204,7 +2195,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
classify_service_install_state(CurrentServiceProbe::Unavailable, true), classify_service_install_state(CurrentServiceProbe::Unavailable, true),
ServiceInstallState::NeedsReinstall ServiceInstallState::Unavailable
); );
} }
@@ -29,7 +29,7 @@ interface ProxyEmptyStateInput {
const isSubscriptionProfile = (item: { type?: string }): boolean => const isSubscriptionProfile = (item: { type?: string }): boolean =>
item.type === 'remote' || item.type === 'local' item.type === 'remote' || item.type === 'local'
export const hasRenderableProxyContent = ( const hasRenderableProxyContent = (
proxyView: ProxyViewV1 | undefined, proxyView: ProxyViewV1 | undefined,
mode: string, mode: string,
isChainMode: boolean, isChainMode: boolean,
@@ -135,7 +135,7 @@ const ProxyControlSwitches = ({
const { uninstallServiceAndStartSidecar } = useServiceUninstaller() const { uninstallServiceAndStartSidecar } = useServiceUninstaller()
const { indicator: systemProxyIndicator, toggleSystemProxy } = const { indicator: systemProxyIndicator, toggleSystemProxy } =
useSystemProxyState() useSystemProxyState()
const { isServiceOk, isTunModeAvailable, mutateSystemState } = const { isServiceInstallReady, isTunModeAvailable, mutateSystemState } =
useSystemState() useSystemState()
const sysproxyRef = useRef<DialogRef>(null) const sysproxyRef = useRef<DialogRef>(null)
@@ -225,7 +225,7 @@ const ProxyControlSwitches = ({
/> />
</> </>
)} )}
{isServiceOk && ( {isServiceInstallReady && (
<TooltipIcon <TooltipIcon
title={t( title={t(
'settings.sections.proxyControl.actions.uninstallService', 'settings.sections.proxyControl.actions.uninstallService',
+9 -8
View File
@@ -16,14 +16,14 @@ import { useVisibility } from './use-visibility'
export interface SystemState { export interface SystemState {
runningMode: RunningMode runningMode: RunningMode
isAdminMode: boolean isAdminMode: boolean
isServiceOk: boolean isServiceInstallReady: boolean
serviceInstallState: ServiceInstallState | null serviceInstallState: ServiceInstallState | null
} }
const defaultSystemState = { const defaultSystemState = {
runningMode: 'Sidecar', runningMode: 'Sidecar',
isAdminMode: false, isAdminMode: false,
isServiceOk: false, isServiceInstallReady: false,
serviceInstallState: null, serviceInstallState: null,
} as SystemState } as SystemState
@@ -36,19 +36,19 @@ export const fetchSystemState = async (): Promise<SystemState> => {
return { return {
runningMode, runningMode,
isAdminMode, isAdminMode,
isServiceOk: serviceInstallState === 'ready', isServiceInstallReady: serviceInstallState === 'ready',
serviceInstallState, serviceInstallState,
} }
} }
const isServiceChoicePending = ( const isServiceChoicePending = (
state: ServiceInstallState | null, state: ServiceInstallState | null,
serviceAvailable: boolean, serviceInstallReady: boolean,
queryFailed: boolean, queryFailed: boolean,
) => ) =>
queryFailed || queryFailed ||
state === 'sidecarAllowed' || state === 'sidecarAllowed' ||
!(state === 'notInstalled' || (state === 'ready' && serviceAvailable)) !(state === 'notInstalled' || (state === 'ready' && serviceInstallReady))
// Grace period for service initialization during startup // Grace period for service initialization during startup
const STARTUP_GRACE_MS = 10_000 const STARTUP_GRACE_MS = 10_000
@@ -81,17 +81,18 @@ export function useSystemState() {
const isSidecarMode = systemState.runningMode === 'Sidecar' const isSidecarMode = systemState.runningMode === 'Sidecar'
const isServiceMode = systemState.runningMode === 'Service' const isServiceMode = systemState.runningMode === 'Service'
const isTunModeAvailable = systemState.isAdminMode || systemState.isServiceOk const isTunModeAvailable =
systemState.isAdminMode || systemState.isServiceInstallReady
const serviceChoicePending = isServiceChoicePending( const serviceChoicePending = isServiceChoicePending(
systemState.serviceInstallState, systemState.serviceInstallState,
systemState.isServiceOk, systemState.isServiceInstallReady,
systemStateError !== undefined, systemStateError !== undefined,
) )
return { return {
runningMode: systemState.runningMode, runningMode: systemState.runningMode,
isAdminMode: systemState.isAdminMode, isAdminMode: systemState.isAdminMode,
isServiceOk: systemState.isServiceOk, isServiceInstallReady: systemState.isServiceInstallReady,
isSidecarMode, isSidecarMode,
isServiceMode, isServiceMode,
isTunModeAvailable, isTunModeAvailable,