mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 19:22:44 +08:00
fix(service): harden transient IPC handling
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user