mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-02 00:04:46 +00:00
refactor(runtime): capture convergence tickets instead of hand-checked generations
execute 现在返回 RuntimeExecutionOutcome(成功与否 + 本次占用的并发流 代际),mode 与 rules 路径的 24 处手写代际 guard 收拢为 ConvergenceTicket 的 convergenceIsCurrent 检查;两处 flowGeneration + 1 手算随之消失。 行为逐字节保持:mode 路径的 mode 代际仍在派生前捕获、preparation 分量 在派生后推进;rules 路径四个代际事实全查不变。
This commit is contained in:
@@ -228,9 +228,8 @@ extension ProxyRuntimeController {
|
||||
guard preparation == runtimePreparationGeneration else { return false }
|
||||
let document = contract.document
|
||||
lastDocument = document
|
||||
guard await execute(.run(document), document: document, preparedContract: contract) else {
|
||||
return false
|
||||
}
|
||||
let execution = await execute(.run(document), document: document, preparedContract: contract)
|
||||
guard execution.succeeded else { return false }
|
||||
guard preparation == runtimePreparationGeneration else { return false }
|
||||
state = .starting
|
||||
return await presentLaunchHealth(
|
||||
@@ -276,15 +275,26 @@ extension ProxyRuntimeController {
|
||||
// MARK: - 计划动作执行
|
||||
|
||||
extension ProxyRuntimeController {
|
||||
/// 按计划顺序执行动作;返回 false 表示中途失败、状态已呈现(后续动作与
|
||||
/// 健康探测都不应继续)。
|
||||
/// 把「运行定义档」意图推到运行时:计划动作 → 顺序执行。返回全部动作
|
||||
/// 是否成功与本次占用的并发流代际。
|
||||
func execute(
|
||||
_ intent: RuntimeIntent, document: SslocalRuntimeDocument?,
|
||||
preparedContract: PreparedRuntimeContract? = nil
|
||||
) async -> Bool {
|
||||
) async -> RuntimeExecutionOutcome {
|
||||
cancelFirewallObservation()
|
||||
flowGeneration += 1
|
||||
let generation = flowGeneration
|
||||
let flow = flowGeneration
|
||||
let succeeded = await performPlannedActions(
|
||||
intent, document: document, preparedContract: preparedContract, flow: flow)
|
||||
return RuntimeExecutionOutcome(flow: flow, succeeded: succeeded)
|
||||
}
|
||||
|
||||
/// 按计划顺序执行动作;返回 false 表示中途失败、状态已呈现(后续动作与
|
||||
/// 健康探测都不应继续)。
|
||||
private func performPlannedActions(
|
||||
_ intent: RuntimeIntent, document: SslocalRuntimeDocument?,
|
||||
preparedContract: PreparedRuntimeContract?, flow: Int
|
||||
) async -> Bool {
|
||||
let preparation = runtimePreparationGeneration
|
||||
let contract: PreparedRuntimeContract?
|
||||
if case .run(let desired) = intent {
|
||||
@@ -307,11 +317,11 @@ extension ProxyRuntimeController {
|
||||
RuntimeLog.emit(.contractUnchanged)
|
||||
}
|
||||
for action in actions {
|
||||
guard generation == flowGeneration, preparation == runtimePreparationGeneration,
|
||||
guard flow == flowGeneration, preparation == runtimePreparationGeneration,
|
||||
await perform(action, preparedContract: contract)
|
||||
else { return false }
|
||||
}
|
||||
return generation == flowGeneration && preparation == runtimePreparationGeneration
|
||||
return flow == flowGeneration && preparation == runtimePreparationGeneration
|
||||
}
|
||||
|
||||
/// 执行单个动作;返回 false 表示应终止后续动作(状态已呈现)。
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
|
||||
/// 一次计划执行的结果:动作是否全部按序成功,以及本次执行占用的并发流
|
||||
/// 代际。占用代际供收敛票据推进 flow 分量,取代调用方对 execute 内部
|
||||
/// 「恰好自增一次」时序的依赖(原 `flowGeneration + 1` 手算)。
|
||||
struct RuntimeExecutionOutcome {
|
||||
/// 本次执行占用的 `flowGeneration`。
|
||||
let flow: Int
|
||||
/// 全部计划动作是否按序成功。
|
||||
let succeeded: Bool
|
||||
}
|
||||
|
||||
/// 收敛票据:运行时收敛操作的代际快照。入口捕获三个并发代际,之后每步
|
||||
/// 推进用它判定「从入口到现在,并发流 / 模式切换 / 运行时派生是否前进了」;
|
||||
/// 被更新提交或新意图取代的旧收敛不得再变更运行时状态或写契约。
|
||||
///
|
||||
/// 各路径校验的代际子集不同:mode 切换的 mode 代际在派生前捕获、派生
|
||||
/// 代际在派生后推进(派生自身会推进它);rules 部署四个事实全查(含
|
||||
/// 会话内 agent 意图);目录 / 启动路径只查派生代际。
|
||||
struct ConvergenceTicket: Equatable {
|
||||
/// 票据时效校验覆盖的代际事实子集。
|
||||
struct Checks: OptionSet {
|
||||
let rawValue: Int
|
||||
/// 并发流代际:任何后续 execute 都使票据失效。
|
||||
static let flow = Checks(rawValue: 1 << 0)
|
||||
/// 模式切换代际。
|
||||
static let mode = Checks(rawValue: 1 << 1)
|
||||
/// 运行时文档派生代际。
|
||||
static let preparation = Checks(rawValue: 1 << 2)
|
||||
/// 会话内 agent 意图仍开启。
|
||||
static let agentEnabled = Checks(rawValue: 1 << 3)
|
||||
}
|
||||
|
||||
/// 并发流代际;每次 execute 之后由调用方以实际占用值推进。
|
||||
var flow: Int
|
||||
/// 模式切换代际;捕获后不再变化。
|
||||
let mode: Int
|
||||
/// 运行时文档派生代际;每次派生之后由调用方以当前值推进。
|
||||
var preparation: Int
|
||||
}
|
||||
|
||||
extension ProxyRuntimeController {
|
||||
/// 以当前代际捕获收敛票据。
|
||||
func convergenceTicket() -> ConvergenceTicket {
|
||||
ConvergenceTicket(
|
||||
flow: flowGeneration, mode: modeChangeGeneration,
|
||||
preparation: runtimePreparationGeneration)
|
||||
}
|
||||
|
||||
/// 票据是否仍代表当前收敛:`checking` 列出的代际事实全部未前进。
|
||||
func convergenceIsCurrent(
|
||||
_ ticket: ConvergenceTicket, checking: ConvergenceTicket.Checks
|
||||
) -> Bool {
|
||||
var current = true
|
||||
if checking.contains(.flow) { current = current && ticket.flow == flowGeneration }
|
||||
if checking.contains(.mode) { current = current && ticket.mode == modeChangeGeneration }
|
||||
if checking.contains(.preparation) {
|
||||
current = current && ticket.preparation == runtimePreparationGeneration
|
||||
}
|
||||
if checking.contains(.agentEnabled) { current = current && settings.agentEnabled }
|
||||
return current
|
||||
}
|
||||
}
|
||||
@@ -100,43 +100,37 @@ extension ProxyRuntimeController {
|
||||
return .runtimeUnchanged
|
||||
}
|
||||
|
||||
let preparation = runtimePreparationGeneration
|
||||
modeChangeGeneration += 1
|
||||
let generation = modeChangeGeneration
|
||||
let runtimeGeneration = flowGeneration + 1
|
||||
var ticket = convergenceTicket()
|
||||
let checks: ConvergenceTicket.Checks = [.flow, .mode, .preparation, .agentEnabled]
|
||||
let contract = try? PreparedRuntimeContract(nextDocument)
|
||||
lastDocument = nextDocument
|
||||
state = .starting
|
||||
guard await execute(.run(nextDocument), document: nextDocument, preparedContract: contract)
|
||||
else {
|
||||
guard ruleDeploymentIsCurrent(preparation, generation, runtimeGeneration)
|
||||
else {
|
||||
return .runtimeChanged(rulesRestored: false)
|
||||
}
|
||||
return await restoreCustomRules(previousRules, snapshot: snapshot)
|
||||
}
|
||||
|
||||
guard ruleDeploymentIsCurrent(preparation, generation, runtimeGeneration)
|
||||
else {
|
||||
let execution = await execute(
|
||||
.run(nextDocument), document: nextDocument, preparedContract: contract)
|
||||
ticket.flow = execution.flow
|
||||
guard convergenceIsCurrent(ticket, checking: checks) else {
|
||||
return .runtimeChanged(rulesRestored: false)
|
||||
}
|
||||
guard execution.succeeded else {
|
||||
return await restoreCustomRules(previousRules, snapshot: snapshot, ticket: ticket)
|
||||
}
|
||||
|
||||
let healthy = await presentLaunchHealth(
|
||||
nextDocument,
|
||||
requiresReceipt: true,
|
||||
convergeProxyOnSuccess: false,
|
||||
preserveProxyOnFailure: true, preparedContract: contract)
|
||||
guard ruleDeploymentIsCurrent(preparation, generation, runtimeGeneration)
|
||||
else {
|
||||
guard convergenceIsCurrent(ticket, checking: checks) else {
|
||||
return .runtimeChanged(rulesRestored: false)
|
||||
}
|
||||
guard healthy else {
|
||||
return await restoreCustomRules(previousRules, snapshot: snapshot)
|
||||
return await restoreCustomRules(previousRules, snapshot: snapshot, ticket: ticket)
|
||||
}
|
||||
|
||||
lastDocument = nextDocument
|
||||
await convergeSystemProxy()
|
||||
guard ruleDeploymentIsCurrent(preparation, generation, runtimeGeneration)
|
||||
else {
|
||||
guard convergenceIsCurrent(ticket, checking: checks) else {
|
||||
return .runtimeChanged(rulesRestored: false)
|
||||
}
|
||||
return .applied
|
||||
@@ -154,13 +148,6 @@ extension ProxyRuntimeController {
|
||||
}
|
||||
}
|
||||
|
||||
private func ruleDeploymentIsCurrent(
|
||||
_ preparation: Int, _ mode: Int, _ runtime: Int
|
||||
) -> Bool {
|
||||
preparation == runtimePreparationGeneration && mode == modeChangeGeneration
|
||||
&& runtime == flowGeneration && settings.agentEnabled
|
||||
}
|
||||
|
||||
/// 保存前校验(issue #66 AC2):固定本地冲突与重复整批拒绝并返回可解释原因。
|
||||
/// 同行动覆盖和相反行动遮蔽不阻止保存;编译时按当前骨架解释完整集合。
|
||||
func validateCustomRulesForPersistence(_ rules: [CustomRule]) -> (
|
||||
@@ -177,8 +164,10 @@ extension ProxyRuntimeController {
|
||||
|
||||
private func restoreCustomRules(
|
||||
_ previousRules: CustomRuleDocument,
|
||||
snapshot: ModeTransitionSnapshotForRules
|
||||
snapshot: ModeTransitionSnapshotForRules,
|
||||
ticket: ConvergenceTicket
|
||||
) async -> CustomRuleUpdateOutcome {
|
||||
var ticket = ticket
|
||||
var failures: [String] = []
|
||||
do {
|
||||
try ruleDocuments.save(previousRules)
|
||||
@@ -195,19 +184,17 @@ extension ProxyRuntimeController {
|
||||
guard settings.agentEnabled else {
|
||||
return .runtimeChanged(rulesRestored: rulesRestored)
|
||||
}
|
||||
let modeGeneration = modeChangeGeneration
|
||||
let preparation = runtimePreparationGeneration
|
||||
let runtimeGeneration = flowGeneration + 1
|
||||
let checks: ConvergenceTicket.Checks = [.flow, .mode, .preparation, .agentEnabled]
|
||||
let contract = try? PreparedRuntimeContract(previousDocument)
|
||||
lastDocument = previousDocument
|
||||
state = .starting
|
||||
let executed = await execute(
|
||||
let execution = await execute(
|
||||
.run(previousDocument), document: previousDocument, preparedContract: contract)
|
||||
guard ruleDeploymentIsCurrent(preparation, modeGeneration, runtimeGeneration)
|
||||
else {
|
||||
ticket.flow = execution.flow
|
||||
guard convergenceIsCurrent(ticket, checking: checks) else {
|
||||
return .runtimeChanged(rulesRestored: rulesRestored)
|
||||
}
|
||||
guard executed else {
|
||||
guard execution.succeeded else {
|
||||
failures.append(String(describing: state))
|
||||
await holdSystemProxyIntent()
|
||||
return .recoveryFailed(detail: failures.joined(separator: "; "), rulesRestored: rulesRestored)
|
||||
@@ -215,8 +202,7 @@ extension ProxyRuntimeController {
|
||||
let healthy = await presentLaunchHealth(
|
||||
previousDocument, requiresReceipt: true, convergeProxyOnSuccess: false,
|
||||
preparedContract: contract)
|
||||
guard ruleDeploymentIsCurrent(preparation, modeGeneration, runtimeGeneration)
|
||||
else {
|
||||
guard convergenceIsCurrent(ticket, checking: checks) else {
|
||||
return .runtimeChanged(rulesRestored: rulesRestored)
|
||||
}
|
||||
if !healthy {
|
||||
|
||||
@@ -51,7 +51,7 @@ extension ProxyRuntimeController {
|
||||
settings = next
|
||||
proxyMode = mode
|
||||
modeChangeGeneration += 1
|
||||
let generation = modeChangeGeneration
|
||||
var ticket = convergenceTicket()
|
||||
|
||||
guard settings.agentEnabled, state != .off,
|
||||
let currentDocument = lastDocument ?? runtimeFileStore.loadDocument()
|
||||
@@ -68,10 +68,13 @@ extension ProxyRuntimeController {
|
||||
} catch {
|
||||
// 快照缺失/损坏:不静默退化,恢复旧模式与旧子选项。
|
||||
RuntimeLog.emit(.runtimePersistFailed(detail: String(describing: error)))
|
||||
ticket.preparation = runtimePreparationGeneration
|
||||
await restoreModeTransition(
|
||||
snapshot: snapshot.resolvingDocument(currentDocument), generation: generation)
|
||||
snapshot: snapshot.resolvingDocument(currentDocument), ticket: ticket)
|
||||
return
|
||||
}
|
||||
// 派生自身推进了派生代际:票据以派生后的当前值推进 preparation 分量。
|
||||
ticket.preparation = runtimePreparationGeneration
|
||||
guard nextDocument.aclRuntime != currentDocument.aclRuntime else {
|
||||
guard settings.systemProxyEnabled else { return }
|
||||
state = .starting
|
||||
@@ -80,8 +83,7 @@ extension ProxyRuntimeController {
|
||||
}
|
||||
|
||||
await deployModeTransition(
|
||||
nextDocument, snapshot: snapshot.resolvingDocument(currentDocument),
|
||||
generation: generation, preparation: runtimePreparationGeneration)
|
||||
nextDocument, snapshot: snapshot.resolvingDocument(currentDocument), ticket: ticket)
|
||||
}
|
||||
|
||||
enum RulePreparationError: Error { case superseded }
|
||||
@@ -167,23 +169,24 @@ extension ProxyRuntimeController {
|
||||
private func deployModeTransition(
|
||||
_ document: SslocalRuntimeDocument,
|
||||
snapshot: ModeTransitionSnapshot,
|
||||
generation: Int, preparation: Int
|
||||
ticket: ConvergenceTicket
|
||||
) async {
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
let contract = try? PreparedRuntimeContract(document)
|
||||
lastDocument = document
|
||||
state = .starting
|
||||
guard await execute(.run(document), document: document, preparedContract: contract) else {
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
let execution = await execute(.run(document), document: document, preparedContract: contract)
|
||||
guard execution.succeeded else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
await restoreModeTransition(snapshot: snapshot, generation: generation)
|
||||
await restoreModeTransition(snapshot: snapshot, ticket: ticket)
|
||||
return
|
||||
}
|
||||
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
let healthy = await presentLaunchHealth(
|
||||
@@ -191,11 +194,11 @@ extension ProxyRuntimeController {
|
||||
requiresReceipt: true,
|
||||
convergeProxyOnSuccess: false,
|
||||
preserveProxyOnFailure: true, preparedContract: contract)
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
guard healthy else {
|
||||
await restoreModeTransition(snapshot: snapshot, generation: generation)
|
||||
await restoreModeTransition(snapshot: snapshot, ticket: ticket)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,9 +208,8 @@ extension ProxyRuntimeController {
|
||||
|
||||
private func restoreModeTransition(
|
||||
snapshot: ModeTransitionSnapshot,
|
||||
generation: Int
|
||||
ticket: ConvergenceTicket
|
||||
) async {
|
||||
let preparation = runtimePreparationGeneration
|
||||
// 两个调用点都经 resolvingDocument 补齐文档;防御性解包失败即无事可做。
|
||||
guard let previousDocument = snapshot.document else { return }
|
||||
let restoredSettings = restoredSettings(for: snapshot)
|
||||
@@ -218,7 +220,7 @@ extension ProxyRuntimeController {
|
||||
persistenceFailed = true
|
||||
RuntimeLog.emit(.runtimePersistFailed(detail: String(describing: error)))
|
||||
}
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
settings = restoredSettings
|
||||
@@ -228,10 +230,10 @@ extension ProxyRuntimeController {
|
||||
let contract = try? PreparedRuntimeContract(previousDocument)
|
||||
guard
|
||||
await restoreRuntimeDocument(
|
||||
previousDocument, generation: generation, preparedContract: contract)
|
||||
previousDocument, ticket: ticket, preparedContract: contract)
|
||||
else { return }
|
||||
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
state = .starting
|
||||
@@ -239,7 +241,7 @@ extension ProxyRuntimeController {
|
||||
previousDocument,
|
||||
requiresReceipt: true,
|
||||
convergeProxyOnSuccess: false, preparedContract: contract)
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return
|
||||
}
|
||||
guard restored else {
|
||||
@@ -267,7 +269,7 @@ extension ProxyRuntimeController {
|
||||
/// 代理并返回 false(健康检查由调用方继续)。
|
||||
private func restoreRuntimeDocument(
|
||||
_ previousDocument: SslocalRuntimeDocument,
|
||||
generation: Int, preparedContract: PreparedRuntimeContract?
|
||||
ticket: ConvergenceTicket, preparedContract: PreparedRuntimeContract?
|
||||
) async -> Bool {
|
||||
let expectedDigest = preparedContract?.sha256 ?? previousDocument.deploymentSHA256
|
||||
let currentDocument = runtimeFileStore.loadDocument()
|
||||
@@ -298,7 +300,7 @@ extension ProxyRuntimeController {
|
||||
if !previousInstanceIsRunning {
|
||||
guard
|
||||
await relaunchPreviousInstance(
|
||||
wrapper: wrapper, previousDocument: previousDocument, generation: generation,
|
||||
wrapper: wrapper, previousDocument: previousDocument, ticket: ticket,
|
||||
preparedContract: preparedContract)
|
||||
else { return false }
|
||||
}
|
||||
@@ -310,9 +312,8 @@ extension ProxyRuntimeController {
|
||||
private func relaunchPreviousInstance(
|
||||
wrapper: WrapperProcessState,
|
||||
previousDocument: SslocalRuntimeDocument,
|
||||
generation: Int, preparedContract: PreparedRuntimeContract?
|
||||
ticket: ConvergenceTicket, preparedContract: PreparedRuntimeContract?
|
||||
) async -> Bool {
|
||||
let preparation = runtimePreparationGeneration
|
||||
switch wrapper {
|
||||
case .running(let pid):
|
||||
guard sendSignal(pid, SIGUSR1) == 0 else {
|
||||
@@ -321,12 +322,11 @@ extension ProxyRuntimeController {
|
||||
return false
|
||||
}
|
||||
case .notRunning:
|
||||
guard
|
||||
await execute(
|
||||
.run(previousDocument), document: previousDocument,
|
||||
preparedContract: preparedContract)
|
||||
else {
|
||||
guard generation == modeChangeGeneration, preparation == runtimePreparationGeneration else {
|
||||
let execution = await execute(
|
||||
.run(previousDocument), document: previousDocument,
|
||||
preparedContract: preparedContract)
|
||||
guard execution.succeeded else {
|
||||
guard convergenceIsCurrent(ticket, checking: [.mode, .preparation]) else {
|
||||
return false
|
||||
}
|
||||
await holdSystemProxyIntent()
|
||||
|
||||
@@ -26,7 +26,7 @@ extension ProxyRuntimeControllerTests {
|
||||
await fulfillment(of: [started], timeout: 3)
|
||||
try FileManager.default.removeItem(at: runtimeStore.pidFileURL)
|
||||
let completed = await oldOperation.value
|
||||
XCTAssertFalse(completed)
|
||||
XCTAssertFalse(completed.succeeded)
|
||||
XCTAssertEqual(runtimeStore.loadDocument(), previous, "Stale stop must not delete the contract")
|
||||
loader.release.signal()
|
||||
await newMode.value
|
||||
|
||||
Reference in New Issue
Block a user