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 crate::{
core::service,
core::{CoreManager, manager::RunningMode, service},
singleton,
utils::dirs::{self, sidecar_log_dir},
};
const fn should_sync_service_writer(running_mode: RunningMode) -> bool {
matches!(running_mode, RunningMode::Service)
}
pub struct Logger {
handle: Arc<Mutex<Option<LoggerHandle>>>,
sidecar_file_writer: Arc<RwLock<Option<FileLogWriter>>>,
@@ -178,14 +182,17 @@ impl Logger {
let sidecar_writer = self.generate_sidecar_writer()?;
*self.sidecar_file_writer.write() = Some(sidecar_writer);
// update service writer config
if service::SERVICE_MANAGER.is_ready_cached() {
service::update_writer_by_service(&clash_verge_service_ipc::WriterConfig {
// The service writer is auxiliary to the local logger. Synchronize it only
// for an active service session and do not roll back local settings on failure.
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(),
max_log_size: log_max_size * 1024,
max_log_files: log_max_count,
})
.await?;
.await
{
logging!(warn, Type::Service, "failed to update service writer config: {error:#}");
}
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 {
CurrentServiceProbe::Ready => ServiceInstallState::Ready,
CurrentServiceProbe::VersionMismatch => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Unavailable if has_install_marker => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Unavailable => ServiceInstallState::Unavailable,
CurrentServiceProbe::Missing if has_install_marker => ServiceInstallState::NeedsReinstall,
CurrentServiceProbe::Missing => ServiceInstallState::NotInstalled,
@@ -304,7 +303,7 @@ fn trusted_service_evidence() -> Result<bool> {
}
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,
Err(error) => {
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);
}
@@ -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 {
@@ -1412,7 +1411,6 @@ impl ServiceManager {
}
}
#[cfg(target_os = "windows")]
pub async fn confirm_ready(&self) -> Result<()> {
let reply = probe_service_version_once()
.await
@@ -1545,14 +1543,7 @@ impl ServiceManager {
}
pub async fn refresh(&self) -> Result<()> {
self.run_operation(async {
apply_service_version_result(
self,
probe_service_version_once().await,
"failed to refresh service protocol",
)
})
.await
self.run_operation(async { self.confirm_ready().await }).await
}
pub async fn handle_service_status(&self, status: ServiceStatus) -> Result<()> {
@@ -2204,7 +2195,7 @@ mod tests {
);
assert_eq!(
classify_service_install_state(CurrentServiceProbe::Unavailable, true),
ServiceInstallState::NeedsReinstall
ServiceInstallState::Unavailable
);
}
@@ -29,7 +29,7 @@ interface ProxyEmptyStateInput {
const isSubscriptionProfile = (item: { type?: string }): boolean =>
item.type === 'remote' || item.type === 'local'
export const hasRenderableProxyContent = (
const hasRenderableProxyContent = (
proxyView: ProxyViewV1 | undefined,
mode: string,
isChainMode: boolean,
@@ -135,7 +135,7 @@ const ProxyControlSwitches = ({
const { uninstallServiceAndStartSidecar } = useServiceUninstaller()
const { indicator: systemProxyIndicator, toggleSystemProxy } =
useSystemProxyState()
const { isServiceOk, isTunModeAvailable, mutateSystemState } =
const { isServiceInstallReady, isTunModeAvailable, mutateSystemState } =
useSystemState()
const sysproxyRef = useRef<DialogRef>(null)
@@ -225,7 +225,7 @@ const ProxyControlSwitches = ({
/>
</>
)}
{isServiceOk && (
{isServiceInstallReady && (
<TooltipIcon
title={t(
'settings.sections.proxyControl.actions.uninstallService',
+9 -8
View File
@@ -16,14 +16,14 @@ import { useVisibility } from './use-visibility'
export interface SystemState {
runningMode: RunningMode
isAdminMode: boolean
isServiceOk: boolean
isServiceInstallReady: boolean
serviceInstallState: ServiceInstallState | null
}
const defaultSystemState = {
runningMode: 'Sidecar',
isAdminMode: false,
isServiceOk: false,
isServiceInstallReady: false,
serviceInstallState: null,
} as SystemState
@@ -36,19 +36,19 @@ export const fetchSystemState = async (): Promise<SystemState> => {
return {
runningMode,
isAdminMode,
isServiceOk: serviceInstallState === 'ready',
isServiceInstallReady: serviceInstallState === 'ready',
serviceInstallState,
}
}
const isServiceChoicePending = (
state: ServiceInstallState | null,
serviceAvailable: boolean,
serviceInstallReady: boolean,
queryFailed: boolean,
) =>
queryFailed ||
state === 'sidecarAllowed' ||
!(state === 'notInstalled' || (state === 'ready' && serviceAvailable))
!(state === 'notInstalled' || (state === 'ready' && serviceInstallReady))
// Grace period for service initialization during startup
const STARTUP_GRACE_MS = 10_000
@@ -81,17 +81,18 @@ export function useSystemState() {
const isSidecarMode = systemState.runningMode === 'Sidecar'
const isServiceMode = systemState.runningMode === 'Service'
const isTunModeAvailable = systemState.isAdminMode || systemState.isServiceOk
const isTunModeAvailable =
systemState.isAdminMode || systemState.isServiceInstallReady
const serviceChoicePending = isServiceChoicePending(
systemState.serviceInstallState,
systemState.isServiceOk,
systemState.isServiceInstallReady,
systemStateError !== undefined,
)
return {
runningMode: systemState.runningMode,
isAdminMode: systemState.isAdminMode,
isServiceOk: systemState.isServiceOk,
isServiceInstallReady: systemState.isServiceInstallReady,
isSidecarMode,
isServiceMode,
isTunModeAvailable,