fix: resolve race conditions in config writes and profile switching

This commit is contained in:
xmk23333 2026-01-01 11:51:44 +08:00
parent 8384953fb7
commit 2c639d5bff
3 changed files with 44 additions and 49 deletions

View File

@ -11,6 +11,7 @@ import { createLogger } from '../utils/logger'
const controledMihomoLogger = createLogger('ControledMihomo') const controledMihomoLogger = createLogger('ControledMihomo')
let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml
let controledMihomoWriteQueue: Promise<void> = Promise.resolve()
export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> { export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> {
if (force || !controledMihomoConfig) { if (force || !controledMihomoConfig) {
@ -39,29 +40,32 @@ export async function getControledMihomoConfig(force = false): Promise<Partial<I
} }
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> { export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
const { controlDns = true, controlSniff = true } = await getAppConfig() controledMihomoWriteQueue = controledMihomoWriteQueue.then(async () => {
const { controlDns = true, controlSniff = true } = await getAppConfig()
if (patch.hosts) { if (patch.hosts) {
controledMihomoConfig.hosts = patch.hosts controledMihomoConfig.hosts = patch.hosts
} }
if (patch.dns?.['nameserver-policy']) { if (patch.dns?.['nameserver-policy']) {
controledMihomoConfig.dns = controledMihomoConfig.dns || {} controledMihomoConfig.dns = controledMihomoConfig.dns || {}
controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy'] controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy']
} }
controledMihomoConfig = deepMerge(controledMihomoConfig, patch) controledMihomoConfig = deepMerge(controledMihomoConfig, patch)
// 从不接管状态恢复 // 从不接管状态恢复
if (controlDns) { if (controlDns) {
// 确保 DNS 配置包含所有必要的默认字段,特别是新增的 fallback 等 // 确保 DNS 配置包含所有必要的默认字段,特别是新增的 fallback 等
controledMihomoConfig.dns = deepMerge( controledMihomoConfig.dns = deepMerge(
defaultControledMihomoConfig.dns || {}, defaultControledMihomoConfig.dns || {},
controledMihomoConfig.dns || {} controledMihomoConfig.dns || {}
) )
} }
if (controlSniff && !controledMihomoConfig.sniffer) { if (controlSniff && !controledMihomoConfig.sniffer) {
controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer
} }
await generateProfile() await generateProfile()
await writeFile(controledMihomoConfigPath(), stringify(controledMihomoConfig), 'utf-8') await writeFile(controledMihomoConfigPath(), stringify(controledMihomoConfig), 'utf-8')
})
await controledMihomoWriteQueue
} }

View File

@ -20,7 +20,7 @@ const profileLogger = createLogger('Profile')
let profileConfig: IProfileConfig let profileConfig: IProfileConfig
let profileConfigWriteQueue: Promise<void> = Promise.resolve() let profileConfigWriteQueue: Promise<void> = Promise.resolve()
let targetProfileId: string | null = null let changeProfileQueue: Promise<void> = Promise.resolve()
export async function getProfileConfig(force = false): Promise<IProfileConfig> { export async function getProfileConfig(force = false): Promise<IProfileConfig> {
if (force || !profileConfig) { if (force || !profileConfig) {
@ -63,37 +63,27 @@ export async function getProfileItem(id: string | undefined): Promise<IProfileIt
} }
export async function changeCurrentProfile(id: string): Promise<void> { export async function changeCurrentProfile(id: string): Promise<void> {
const { current } = await getProfileConfig() // 使用队列确保 profile 切换串行执行,避免竞态条件
changeProfileQueue = changeProfileQueue.then(async () => {
const { current } = await getProfileConfig()
if (current === id) return
if (current === id && targetProfileId !== id) { try {
return await updateProfileConfig((config) => {
} config.current = id
return config
targetProfileId = id })
await restartCore()
try { } catch (e) {
await updateProfileConfig((config) => { // 回滚配置
config.current = id
return config
})
if (targetProfileId !== id) {
return
}
await restartCore()
if (targetProfileId === id) {
targetProfileId = null
}
} catch (e) {
if (targetProfileId === id) {
await updateProfileConfig((config) => { await updateProfileConfig((config) => {
config.current = current config.current = current
return config return config
}) })
targetProfileId = null
throw e throw e
} }
} })
await changeProfileQueue
} }
export async function updateProfileItem(item: IProfileItem): Promise<void> { export async function updateProfileItem(item: IProfileItem): Promise<void> {

View File

@ -6,7 +6,7 @@ import { getAppConfig } from './config'
import { quitWithoutCore, stopCore } from './core/manager' import { quitWithoutCore, stopCore } from './core/manager'
import { triggerSysProxy } from './sys/sysproxy' import { triggerSysProxy } from './sys/sysproxy'
import { hideDockIcon, showDockIcon } from './resolve/tray' import { hideDockIcon, showDockIcon } from './resolve/tray'
import icon from '../resources/icon.png?asset' import icon from '../../resources/icon.png?asset'
export let mainWindow: BrowserWindow | null = null export let mainWindow: BrowserWindow | null = null
let quitTimeout: NodeJS.Timeout | null = null let quitTimeout: NodeJS.Timeout | null = null
@ -78,7 +78,8 @@ function setupWindowEvents(
scheduleQuitWithoutCore(autoQuitWithoutCoreDelay) scheduleQuitWithoutCore(autoQuitWithoutCoreDelay)
} }
if (!silentStart) { // 开发模式下始终显示窗口
if (!silentStart || is.dev) {
clearQuitTimeout() clearQuitTimeout()
window.show() window.show()
window.focusOnWebView() window.focusOnWebView()