mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-30 22:23:43 +00:00
refactor(sysproxy): 观察机写面收成意图命令,字段退回实现
This commit is contained in:
@@ -69,10 +69,11 @@ extension ProxyRuntimeController {
|
||||
} else {
|
||||
let cleanupOutcome = await cascadeSystemProxyOffForAgentOff()
|
||||
await stopAgent()
|
||||
if let cleanupOutcome, cleanupOutcome.hasOperationFailure {
|
||||
systemProxyObserver.systemProxyState = cleanupOutcome
|
||||
if let cleanupOutcome {
|
||||
systemProxyObserver.presentCleanupOutcome(cleanupOutcome)
|
||||
} else {
|
||||
systemProxyObserver.updateSystemProxyActions()
|
||||
}
|
||||
systemProxyObserver.updateSystemProxyActions()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,47 +105,24 @@ extension ProxyRuntimeController {
|
||||
guard persistSettings(next) else { return }
|
||||
settings = next
|
||||
if enabled {
|
||||
systemProxyObserver.systemProxyInitialApplyPending = true
|
||||
systemProxyObserver.systemProxyWasUnavailable = false
|
||||
await systemProxyObserver.convergeSystemProxy()
|
||||
systemProxyObserver.startEnabledSystemProxyObservation()
|
||||
await systemProxyObserver.enableSystemProxyIntent()
|
||||
} else {
|
||||
systemProxyObserver.systemProxyApprovalRequired = false
|
||||
systemProxyObserver.systemProxyInitialApplyPending = false
|
||||
systemProxyObserver.systemProxyReadGeneration += 1
|
||||
systemProxyObserver.systemProxyState =
|
||||
await systemProxyObserver.clearAndStopSystemProxyObservation()
|
||||
systemProxyObserver.updateSystemProxyActions()
|
||||
await systemProxyObserver.disableSystemProxyIntent()
|
||||
}
|
||||
}
|
||||
|
||||
/// GUI startup restores the Agent. Healthy proxy intent only inspects; unhealthy
|
||||
/// proxy intent suspends; disabled intent never writes SystemConfiguration.
|
||||
func resyncOnLaunch() async {
|
||||
systemProxyObserver.systemProxyStartupInProgress = true
|
||||
systemProxyObserver.beginStartupResync()
|
||||
guard settings.agentEnabled else {
|
||||
await stopAgent()
|
||||
systemProxyObserver.systemProxyStartupInProgress = false
|
||||
if settings.systemProxyEnabled {
|
||||
await systemProxyObserver.suspendSystemProxy()
|
||||
systemProxyObserver.startEnabledSystemProxyObservation()
|
||||
}
|
||||
await systemProxyObserver.settleLaunchWithAgentOff()
|
||||
return
|
||||
}
|
||||
let catalog = catalogSnapshotReader.catalogSnapshot
|
||||
await convergeToReexpanded(catalog)
|
||||
systemProxyObserver.systemProxyStartupInProgress = false
|
||||
if settings.systemProxyEnabled {
|
||||
systemProxyObserver.lastDesiredSystemProxyConfiguration = desiredSystemProxyConfiguration
|
||||
if systemProxyExitAvailable {
|
||||
await systemProxyObserver.recheckSystemProxy()
|
||||
} else {
|
||||
await systemProxyObserver.suspendSystemProxy()
|
||||
}
|
||||
systemProxyObserver.startEnabledSystemProxyObservation()
|
||||
} else {
|
||||
systemProxyObserver.systemProxyState = .idle
|
||||
}
|
||||
await systemProxyObserver.settleLaunchAfterAgentConverged()
|
||||
}
|
||||
|
||||
// MARK: - 动作执行
|
||||
@@ -159,23 +137,12 @@ extension ProxyRuntimeController {
|
||||
/// 系统代理状态按意图事实收尾——级联后意图已关闭为空闲;残留的「意图开
|
||||
/// 启 + agent 关闭」组合保持待应用(不静默清理,也无需网络观察)。
|
||||
func stopAgent() async {
|
||||
systemProxyNetworkChangeMonitor.stop()
|
||||
systemProxyObserver.systemProxyHealthTask?.cancel()
|
||||
systemProxyObserver.systemProxyHealthTask = nil
|
||||
systemProxyObserver.systemProxyObservationMode = .stopped
|
||||
systemProxyObserver.agentDidStop()
|
||||
_ = await execute(.stop, document: nil)
|
||||
state = .off
|
||||
lastDocument = nil
|
||||
skippedServers = []
|
||||
lastActivationFailure = nil
|
||||
if settings.systemProxyEnabled {
|
||||
systemProxyObserver.systemProxyState = .pending
|
||||
} else {
|
||||
if !systemProxyState.hasOperationFailure {
|
||||
systemProxyObserver.systemProxyState = .idle
|
||||
}
|
||||
systemProxyObserver.updateSystemProxyActions()
|
||||
}
|
||||
}
|
||||
|
||||
/// 无活动目标时 agent 仍提供本地监听(issue #60):空服务器列表文档,
|
||||
|
||||
@@ -50,7 +50,7 @@ extension ProxyRuntimeController {
|
||||
guard settings.agentEnabled, state != .off,
|
||||
let currentDocument = lastDocument ?? runtimeFileStore.loadDocument()
|
||||
else {
|
||||
if settings.systemProxyEnabled { systemProxyObserver.systemProxyState = .pending }
|
||||
systemProxyObserver.modeTransitionAwaitingRuntime()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -14,23 +14,30 @@ final class SystemProxyObserver: ObservableObject {
|
||||
}
|
||||
|
||||
/// Current configuration or operation result, independent of persisted intent.
|
||||
@Published var systemProxyState: SystemProxyApplicationFacts = .idle
|
||||
@Published private(set) var systemProxyState: SystemProxyApplicationFacts = .idle
|
||||
/// Approval remains actionable even after an off-intent clear failure.
|
||||
@Published var systemProxyApprovalRequired = false
|
||||
@Published var systemProxyInspection = SystemProxyInspectionFacts()
|
||||
@Published private(set) var systemProxyApprovalRequired = false
|
||||
@Published private(set) var systemProxyInspection = SystemProxyInspectionFacts()
|
||||
|
||||
var lastDesiredSystemProxyConfiguration: SystemProxyConfiguration?
|
||||
var knownSystemProxyServices: Set<SystemProxyServiceIdentifier> = []
|
||||
var systemProxyOperationInProgress = false
|
||||
var systemProxyStartupInProgress = false
|
||||
var systemProxyInitialApplyPending = false
|
||||
var systemProxyWasUnavailable = false
|
||||
// 写面唯一入口是下方「意图命令」;private(set) 的读面仅开给健康循环体
|
||||
// 与观察机测试,其余实现细节一律 private。
|
||||
private var lastDesiredSystemProxyConfiguration: SystemProxyConfiguration?
|
||||
private var knownSystemProxyServices: Set<SystemProxyServiceIdentifier> = []
|
||||
private var systemProxyOperationInProgress = false
|
||||
/// 读面:健康循环体在 launch 重同步抑制期跳过巡检。
|
||||
private(set) var systemProxyStartupInProgress = false
|
||||
/// 读面:健康循环的收敛条件。
|
||||
private(set) var systemProxyInitialApplyPending = false
|
||||
/// 读面:健康循环的收敛条件。
|
||||
private(set) var systemProxyWasUnavailable = false
|
||||
/// 过渡:任务柄创建仍在控制器侧,柄归属收回观察机后转 private。
|
||||
var systemProxyHealthTask: Task<Void, Never>?
|
||||
var systemProxyReadGeneration = 0
|
||||
var systemProxyObservationMode = SystemProxyObservationMode.stopped
|
||||
var systemProxyCleanupRescanRequested = false
|
||||
var systemProxyCleanupTask: Task<SystemProxyApplicationFacts, Never>?
|
||||
var systemProxyInspectionScheduled = false
|
||||
private var systemProxyReadGeneration = 0
|
||||
/// 读面:观察机测试。
|
||||
private(set) var systemProxyObservationMode = SystemProxyObservationMode.stopped
|
||||
private var systemProxyCleanupRescanRequested = false
|
||||
private var systemProxyCleanupTask: Task<SystemProxyApplicationFacts, Never>?
|
||||
private var systemProxyInspectionScheduled = false
|
||||
|
||||
let systemProxy: SystemProxyControlling
|
||||
let systemProxyHelper: SystemProxyHelperServicing
|
||||
@@ -216,28 +223,6 @@ final class SystemProxyObserver: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func refreshSystemProxyApproval() {
|
||||
systemProxyApprovalRequired =
|
||||
systemProxyHelper.status != .approved
|
||||
&& (isIntentEnabled() || systemProxyState.hasOperationFailure)
|
||||
}
|
||||
|
||||
func updateSystemProxyActions() {
|
||||
refreshSystemProxyApproval()
|
||||
let available = !systemProxyOperationInProgress && !systemProxyApprovalRequired
|
||||
systemProxyInspection.canRepair =
|
||||
available && isIntentEnabled()
|
||||
&& isExitAvailable() && systemProxyInspection.readFailure == nil
|
||||
&& (!systemProxyInspection.differences.isEmpty || systemProxyState.isApplyFailure)
|
||||
systemProxyInspection.canRetryClear = available && systemProxyState.isClearFailure
|
||||
if systemProxyState.hasOperationFailure {
|
||||
startSystemProxyHealthObservation()
|
||||
} else if !isIntentEnabled() {
|
||||
systemProxyHealthTask?.cancel()
|
||||
systemProxyHealthTask = nil
|
||||
}
|
||||
}
|
||||
|
||||
func suspendSystemProxy() async {
|
||||
guard isIntentEnabled(), !systemProxyStartupInProgress else { return }
|
||||
guard !systemProxyWasUnavailable else {
|
||||
@@ -296,6 +281,125 @@ final class SystemProxyObserver: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 呈现动作与批准刷新
|
||||
|
||||
extension SystemProxyObserver {
|
||||
func refreshSystemProxyApproval() {
|
||||
systemProxyApprovalRequired =
|
||||
systemProxyHelper.status != .approved
|
||||
&& (isIntentEnabled() || systemProxyState.hasOperationFailure)
|
||||
}
|
||||
|
||||
func updateSystemProxyActions() {
|
||||
refreshSystemProxyApproval()
|
||||
let available = !systemProxyOperationInProgress && !systemProxyApprovalRequired
|
||||
systemProxyInspection.canRepair =
|
||||
available && isIntentEnabled()
|
||||
&& isExitAvailable() && systemProxyInspection.readFailure == nil
|
||||
&& (!systemProxyInspection.differences.isEmpty || systemProxyState.isApplyFailure)
|
||||
systemProxyInspection.canRetryClear = available && systemProxyState.isClearFailure
|
||||
if systemProxyState.hasOperationFailure {
|
||||
startSystemProxyHealthObservation()
|
||||
} else if !isIntentEnabled() {
|
||||
systemProxyHealthTask?.cancel()
|
||||
systemProxyHealthTask = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 意图命令(控制器写面唯一入口)
|
||||
|
||||
extension SystemProxyObserver {
|
||||
/// 系统代理意图开启(setSystemProxyEnabled on):本轮必须真实应用一次——
|
||||
/// 清理期可能已把系统设置清空,不能凭 lastDesired 相等跳过;收敛后转入
|
||||
/// enabled 观察与网络变化巡检。
|
||||
func enableSystemProxyIntent() async {
|
||||
systemProxyInitialApplyPending = true
|
||||
systemProxyWasUnavailable = false
|
||||
await convergeSystemProxy()
|
||||
startEnabledSystemProxyObservation()
|
||||
}
|
||||
|
||||
/// 系统代理意图关闭(setSystemProxyEnabled off):撤销待批准/待应用标记,
|
||||
/// 读取代际推进作废在途读,无条件清理并停观察,结果原样呈现。
|
||||
func disableSystemProxyIntent() async {
|
||||
systemProxyApprovalRequired = false
|
||||
systemProxyInitialApplyPending = false
|
||||
systemProxyReadGeneration += 1
|
||||
systemProxyState = await clearAndStopSystemProxyObservation()
|
||||
updateSystemProxyActions()
|
||||
}
|
||||
|
||||
/// agent 停机的观察收尾(stopAgent):停网络监视与健康循环,观察模式归
|
||||
/// 零。呈现按代理意图分流——意图仍在则保持待应用(等 agent 恢复后收敛,
|
||||
/// issue #71),意图已关则空闲。
|
||||
func agentDidStop() {
|
||||
systemProxyNetworkChangeMonitor.stop()
|
||||
systemProxyHealthTask?.cancel()
|
||||
systemProxyHealthTask = nil
|
||||
systemProxyObservationMode = .stopped
|
||||
if isIntentEnabled() {
|
||||
systemProxyState = .pending
|
||||
} else {
|
||||
if !systemProxyState.hasOperationFailure {
|
||||
systemProxyState = .idle
|
||||
}
|
||||
updateSystemProxyActions()
|
||||
}
|
||||
}
|
||||
|
||||
/// agent 关闭级联的清理结果呈现(setAgentEnabled off):typed 失败原样
|
||||
/// 上屏(清理不重试),成功不覆盖既有呈现。
|
||||
func presentCleanupOutcome(_ outcome: SystemProxyApplicationFacts) {
|
||||
if outcome.hasOperationFailure {
|
||||
systemProxyState = outcome
|
||||
}
|
||||
updateSystemProxyActions()
|
||||
}
|
||||
|
||||
/// mode 切换发现无可收敛运行时(agent 关或无活动文档):代理意图仍开时
|
||||
/// 保持待应用,等运行时恢复后由健康循环收敛(+Mode 提前出口)。
|
||||
func modeTransitionAwaitingRuntime() {
|
||||
if isIntentEnabled() {
|
||||
systemProxyState = .pending
|
||||
}
|
||||
}
|
||||
|
||||
/// launch 重同步开始(resyncOnLaunch 入口):抑制期 converge/suspend 拒绝
|
||||
/// 执行、健康循环跳过巡检;须由 settle 命令解除。
|
||||
func beginStartupResync() {
|
||||
systemProxyStartupInProgress = true
|
||||
}
|
||||
|
||||
/// launch 重同步收尾(agent 意图关闭分支):解除抑制(抑制期 suspend 的
|
||||
/// 守卫会拒绝执行);代理意图仍开则挂起残留配置并转入观察。
|
||||
func settleLaunchWithAgentOff() async {
|
||||
systemProxyStartupInProgress = false
|
||||
if isIntentEnabled() {
|
||||
await suspendSystemProxy()
|
||||
startEnabledSystemProxyObservation()
|
||||
}
|
||||
}
|
||||
|
||||
/// launch 重同步收尾(agent 已收敛分支):解除抑制;代理意图开则播种期望
|
||||
/// 配置(健康 GUI 启动只巡检不强推应用),按出口可用性巡检或挂起,转入
|
||||
/// 观察;意图关呈现空闲。
|
||||
func settleLaunchAfterAgentConverged() async {
|
||||
systemProxyStartupInProgress = false
|
||||
if isIntentEnabled() {
|
||||
lastDesiredSystemProxyConfiguration = desiredConfiguration()
|
||||
if isExitAvailable() {
|
||||
await recheckSystemProxy()
|
||||
} else {
|
||||
await suspendSystemProxy()
|
||||
}
|
||||
startEnabledSystemProxyObservation()
|
||||
} else {
|
||||
systemProxyState = .idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - helper 门禁(issue #71)
|
||||
|
||||
extension SystemProxyObserver {
|
||||
|
||||
@@ -34,7 +34,6 @@ final class SystemProxyObserverTests: XCTestCase {
|
||||
func testConvergeWaitsForInFlightOperationInsteadOfSkipping() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let observer = makeObserver(proxy: proxy, cleanupSettleNanoseconds: 1_000_000)
|
||||
observer.systemProxyInitialApplyPending = true
|
||||
// 拉长首次读服务的窗口,制造进行中的操作。
|
||||
proxy.beforeRead = { _ = try? await Task.sleep(nanoseconds: 30_000_000) }
|
||||
|
||||
@@ -46,11 +45,101 @@ final class SystemProxyObserverTests: XCTestCase {
|
||||
XCTAssertEqual(proxy.applied.count, 2, "并发收敛必须等操作收尾后执行而非被跳过")
|
||||
}
|
||||
|
||||
func testEnableIntentAppliesFreshAndStartsObservation() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let monitor = ProxyRuntimeFixture.FakeSystemProxyNetworkChangeMonitor()
|
||||
let observer = makeObserver(proxy: proxy, monitor: monitor, cleanupSettleNanoseconds: 1_000_000)
|
||||
|
||||
await observer.enableSystemProxyIntent()
|
||||
|
||||
XCTAssertEqual(proxy.applied.count, 1, "开启意图必须真实应用一次(清理期可能已清空系统设置)")
|
||||
XCTAssertEqual(observer.systemProxyState, .applied)
|
||||
XCTAssertEqual(observer.systemProxyObservationMode, .enabled)
|
||||
XCTAssertEqual(monitor.startCount, 1)
|
||||
}
|
||||
|
||||
func testDisableIntentClearsStopsObservationAndPublishesOutcome() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let monitor = ProxyRuntimeFixture.FakeSystemProxyNetworkChangeMonitor()
|
||||
let observer = makeObserver(proxy: proxy, monitor: monitor, cleanupSettleNanoseconds: 1_000_000)
|
||||
await observer.enableSystemProxyIntent()
|
||||
|
||||
await observer.disableSystemProxyIntent()
|
||||
|
||||
XCTAssertEqual(proxy.clearCount, 1)
|
||||
XCTAssertEqual(observer.systemProxyState, .idle)
|
||||
XCTAssertEqual(observer.systemProxyObservationMode, .stopped)
|
||||
XCTAssertEqual(monitor.stopCount, 1)
|
||||
XCTAssertFalse(observer.systemProxyApprovalRequired)
|
||||
}
|
||||
|
||||
func testAgentStopPresentationFollowsProxyIntent() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let monitor = ProxyRuntimeFixture.FakeSystemProxyNetworkChangeMonitor()
|
||||
var intentEnabled = true
|
||||
let observer = makeObserver(
|
||||
proxy: proxy, monitor: monitor, cleanupSettleNanoseconds: 1_000_000,
|
||||
isIntentEnabled: { intentEnabled })
|
||||
|
||||
observer.agentDidStop()
|
||||
XCTAssertEqual(observer.systemProxyState, .pending, "代理意图仍在开时,agent 停机保持待应用(issue #71)")
|
||||
XCTAssertEqual(observer.systemProxyObservationMode, .stopped)
|
||||
XCTAssertEqual(monitor.stopCount, 1)
|
||||
|
||||
intentEnabled = false
|
||||
observer.agentDidStop()
|
||||
XCTAssertEqual(observer.systemProxyState, .idle, "代理意图已关时,agent 停机呈现空闲")
|
||||
}
|
||||
|
||||
func testModeTransitionAwaitingRuntimeStampsPendingOnlyUnderIntent() async throws {
|
||||
var intentEnabled = false
|
||||
let observer = makeObserver(
|
||||
proxy: ProxyRuntimeFixture.FakeSystemProxy(), cleanupSettleNanoseconds: 1_000_000,
|
||||
isIntentEnabled: { intentEnabled })
|
||||
|
||||
observer.modeTransitionAwaitingRuntime()
|
||||
XCTAssertEqual(observer.systemProxyState, .idle, "代理意图关闭时不打待应用图章")
|
||||
|
||||
intentEnabled = true
|
||||
observer.modeTransitionAwaitingRuntime()
|
||||
XCTAssertEqual(observer.systemProxyState, .pending)
|
||||
}
|
||||
|
||||
func testSettleLaunchAfterConvergedInspectsWithoutReapplying() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let observer = makeObserver(proxy: proxy, cleanupSettleNanoseconds: 1_000_000)
|
||||
|
||||
await observer.settleLaunchAfterAgentConverged()
|
||||
|
||||
XCTAssertEqual(proxy.applied.count, 0, "健康的 GUI 启动只巡检,不强推应用")
|
||||
XCTAssertGreaterThanOrEqual(proxy.readCount, 1)
|
||||
XCTAssertEqual(observer.systemProxyObservationMode, .enabled)
|
||||
XCTAssertFalse(observer.systemProxyStartupInProgress)
|
||||
// 播种 lastDesired 后,后续收敛(如网络变化回调)不得因相等而重新应用。
|
||||
await observer.convergeSystemProxy()
|
||||
XCTAssertEqual(proxy.applied.count, 0, "播种 lastDesired 后收敛不得重新应用")
|
||||
}
|
||||
|
||||
func testStartupSuppressionBlocksConvergeUntilSettled() async throws {
|
||||
let proxy = ProxyRuntimeFixture.FakeSystemProxy()
|
||||
let observer = makeObserver(proxy: proxy, cleanupSettleNanoseconds: 1_000_000)
|
||||
observer.beginStartupResync()
|
||||
|
||||
await observer.convergeSystemProxy(forceApply: true)
|
||||
XCTAssertEqual(proxy.applied.count, 0, "launch 重同步抑制期 converge 必须被拒绝")
|
||||
|
||||
await observer.settleLaunchWithAgentOff()
|
||||
XCTAssertFalse(observer.systemProxyStartupInProgress)
|
||||
XCTAssertEqual(observer.systemProxyState, .paused, "抑制解除后按意图挂起残留配置")
|
||||
XCTAssertEqual(observer.systemProxyObservationMode, .enabled)
|
||||
}
|
||||
|
||||
private func makeObserver(
|
||||
proxy: ProxyRuntimeFixture.FakeSystemProxy,
|
||||
monitor: SystemProxyNetworkChangeMonitoring =
|
||||
ProxyRuntimeFixture.FakeSystemProxyNetworkChangeMonitor(),
|
||||
cleanupSettleNanoseconds: UInt64
|
||||
cleanupSettleNanoseconds: UInt64,
|
||||
isIntentEnabled: @escaping @MainActor () -> Bool = { true }
|
||||
) -> SystemProxyObserver {
|
||||
SystemProxyObserver(
|
||||
systemProxy: proxy,
|
||||
@@ -59,7 +148,7 @@ final class SystemProxyObserverTests: XCTestCase {
|
||||
appBundle: AppArtifact.bundle,
|
||||
helperRefreshDelayNanoseconds: 1_000_000,
|
||||
cleanupSettleNanoseconds: cleanupSettleNanoseconds,
|
||||
isIntentEnabled: { true },
|
||||
isIntentEnabled: isIntentEnabled,
|
||||
isExitAvailable: { true },
|
||||
desiredConfiguration: {
|
||||
try? ProxyMode.direct.systemProxyConfiguration(
|
||||
|
||||
Reference in New Issue
Block a user