fix command

This commit is contained in:
anlicheng 2026-06-21 22:38:49 +08:00
parent 5813107425
commit 49750099ea
5 changed files with 157 additions and 19 deletions

View File

@ -149,7 +149,7 @@ actor SDLContextActor {
do { do {
try await self.runRootBody() try await self.runRootBody()
} catch is CancellationError { } catch is CancellationError {
if let terminalError = await self.consumeTerminalError() { if let terminalError = self.consumeTerminalError() {
SDLLogger.fatal("[SDLContext] root task stopped by terminal error: \(terminalError)", category: .context) SDLLogger.fatal("[SDLContext] root task stopped by terminal error: \(terminalError)", category: .context)
result = .failure(terminalError) result = .failure(terminalError)
} else { } else {
@ -163,7 +163,7 @@ actor SDLContextActor {
} }
await self.cleanupRoot() await self.cleanupRoot()
await self.finishRootTask(id: rootTaskID) self.finishRootTask(id: rootTaskID)
try result.get() try result.get()
} }
self.rootTaskID = rootTaskID self.rootTaskID = rootTaskID
@ -196,6 +196,27 @@ actor SDLContextActor {
self.terminalError = nil self.terminalError = nil
} }
public func recoverAfterWake() async throws {
SDLLogger.log("[SDLContext] recoverAfterWake requested", category: .context)
guard self.rootTask != nil else {
throw TunnelError.invalidContext
}
try await self.readySignal.wait(timeout: .seconds(30))
guard let dataCipher = self.dataCipher else {
throw TunnelError.invalidContext
}
let prunedSessions = await self.sessionManager.pruneExpiredSessions()
await self.packetOutboundActor.updateRuntime(config: self.config, dataCipher: dataCipher)
await self.packetInboundActor.updateRuntime(config: self.config, dataCipher: dataCipher)
try await self.tunNetworkManager.apply(settings: .init(config: self.config), dnsServer: DNSHelper.dnsServer)
SDLLogger.log("[SDLContext] recoverAfterWake completed, prunedSessions: \(prunedSessions)", category: .context)
}
private func runRootBody() async throws { private func runRootBody() async throws {
self.prepareTunnelNotifier() self.prepareTunnelNotifier()

View File

@ -8,6 +8,16 @@
import Foundation import Foundation
final class SDLContextBootstrap: @unchecked Sendable { final class SDLContextBootstrap: @unchecked Sendable {
private typealias StartCompletion = (Error?) -> Void
private typealias StopCompletion = () -> Void
private typealias WakeCompletion = (Error?) -> Void
private enum BootstrapCommand {
case start(config: SDLConfiguration, rsaCipher: CCRSACipher, completion: StartCompletion)
case stop(clearRuntimeConfiguration: Bool, completion: StopCompletion)
case recoverAfterWake(completion: WakeCompletion)
}
private enum RuntimeState { private enum RuntimeState {
case idle case idle
case starting case starting
@ -22,9 +32,22 @@ final class SDLContextBootstrap: @unchecked Sendable {
private var rsaCipher: CCRSACipher? private var rsaCipher: CCRSACipher?
private var contextActor: SDLContextActor? private var contextActor: SDLContextActor?
private var startCompletionHandler: ((Error?) -> Void)? private var startCompletionHandler: ((Error?) -> Void)?
private let commandContinuation: AsyncStream<BootstrapCommand>.Continuation
private var commandWorker: Task<Void, Never>?
init(provider: PacketTunnelProvider) { init(provider: PacketTunnelProvider) {
let commandPair = AsyncStream.makeStream(of: BootstrapCommand.self, bufferingPolicy: .unbounded)
self.provider = provider self.provider = provider
self.commandContinuation = commandPair.continuation
self.commandWorker = Task { [weak self, stream = commandPair.stream] in
await self?.runCommandLoop(stream)
}
}
deinit {
self.commandContinuation.finish()
self.commandWorker?.cancel()
} }
func startCached(completionHandler: @escaping (Error?) -> Void) { func startCached(completionHandler: @escaping (Error?) -> Void) {
@ -43,6 +66,46 @@ final class SDLContextBootstrap: @unchecked Sendable {
} }
func start(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) { func start(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) {
self.submit(.start(config: config, rsaCipher: rsaCipher, completion: completionHandler))
}
func stop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) {
self.submit(.stop(clearRuntimeConfiguration: clearRuntimeConfiguration, completion: completionHandler))
}
func recoverAfterWake(completionHandler: @escaping (Error?) -> Void) {
self.submit(.recoverAfterWake(completion: completionHandler))
}
func currentContextActor() -> SDLContextActor? {
self.runtimeLock.lock()
let contextActor = self.contextActor
self.runtimeLock.unlock()
return contextActor
}
private func submit(_ command: BootstrapCommand) {
self.commandContinuation.yield(command)
}
private func runCommandLoop(_ stream: AsyncStream<BootstrapCommand>) async {
for await command in stream {
self.handle(command)
}
}
private func handle(_ command: BootstrapCommand) {
switch command {
case .start(let config, let rsaCipher, let completion):
self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completion)
case .stop(let clearRuntimeConfiguration, let completion):
self.handleStop(clearRuntimeConfiguration: clearRuntimeConfiguration, completionHandler: completion)
case .recoverAfterWake(let completion):
self.handleRecoverAfterWake(completionHandler: completion)
}
}
private func handleStart(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) {
guard let provider = self.provider else { guard let provider = self.provider else {
SDLLogger.fatal("[SDLContextBootstrap] start rejected: provider released", category: .app) SDLLogger.fatal("[SDLContextBootstrap] start rejected: provider released", category: .app)
completionHandler(TunnelError.invalidContext) completionHandler(TunnelError.invalidContext)
@ -84,7 +147,7 @@ final class SDLContextBootstrap: @unchecked Sendable {
} }
} }
func stop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) { private func handleStop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) {
self.runtimeLock.lock() self.runtimeLock.lock()
let contextActor = self.contextActor let contextActor = self.contextActor
let startCompletionHandler = self.startCompletionHandler let startCompletionHandler = self.startCompletionHandler
@ -122,11 +185,50 @@ final class SDLContextBootstrap: @unchecked Sendable {
} }
} }
func currentContextActor() -> SDLContextActor? { private func handleRecoverAfterWake(completionHandler: @escaping (Error?) -> Void) {
self.runtimeLock.lock() self.runtimeLock.lock()
let runtimeState = self.runtimeState
let contextActor = self.contextActor let contextActor = self.contextActor
let config = self.config
let rsaCipher = self.rsaCipher
self.runtimeLock.unlock() self.runtimeLock.unlock()
return contextActor
switch runtimeState {
case .running:
guard let contextActor else {
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing running context", category: .app)
completionHandler(TunnelError.invalidContext)
return
}
Task {
do {
try await contextActor.recoverAfterWake()
completionHandler(nil)
} catch {
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: \(error)", category: .app)
completionHandler(error)
}
}
case .idle:
guard let config, let rsaCipher else {
SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing cached runtime configuration", category: .app)
completionHandler(TunnelError.invalidConfiguration)
return
}
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake will start cached context", category: .app)
self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler)
case .starting:
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is starting", category: .app)
completionHandler(nil)
case .stopping:
SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is stopping", category: .app)
completionHandler(nil)
}
} }
private func finishContextStart(_ contextActor: SDLContextActor, error: Error?) { private func finishContextStart(_ contextActor: SDLContextActor, error: Error?) {

View File

@ -56,21 +56,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
override func sleep(completionHandler: @escaping () -> Void) { override func sleep(completionHandler: @escaping () -> Void) {
SDLLogger.fatal("[PacketTunnelProvider] sleep requested, will stop current context", category: .app) SDLLogger.log("[PacketTunnelProvider] sleep requested", category: .app)
self.contextBootstrap.stop(clearRuntimeConfiguration: false) { completionHandler()
SDLLogger.log("[PacketTunnelProvider] sleep", category: .app)
completionHandler()
}
} }
override func wake() { override func wake() {
SDLLogger.log("[PacketTunnelProvider] wake up!!!!!!!", category: .app) SDLLogger.log("[PacketTunnelProvider] wake up!!!!!!!", category: .app)
self.contextBootstrap.startCached { err in self.contextBootstrap.recoverAfterWake { err in
if let err { if let err {
SDLLogger.fatal("[PacketTunnelProvider] wakeup start failed: \(err)", category: .app) SDLLogger.fatal("[PacketTunnelProvider] wakeup recovery failed: \(err)", category: .app)
SDLLogger.log("[PacketTunnelProvider] wakeup start failed: \(err.localizedDescription)", category: .app) SDLLogger.log("[PacketTunnelProvider] wakeup recovery failed: \(err.localizedDescription)", category: .app)
} else { } else {
SDLLogger.log("[PacketTunnelProvider] wakeup and try start", category: .app) SDLLogger.log("[PacketTunnelProvider] wakeup recovery completed", category: .app)
} }
} }
} }

View File

@ -71,6 +71,15 @@ actor SessionManager {
self.publishSnapshot() self.publishSnapshot()
} }
@discardableResult
func pruneExpiredSessions() -> Int {
let oldCount = self.sessionCount()
self.sessions = self.validSessions()
let newCount = self.sessionCount()
self.publishSnapshot()
return oldCount - newCount
}
nonisolated func snapshot() -> SessionSnapshot { nonisolated func snapshot() -> SessionSnapshot {
return self.snapshotPublisher.current() return self.snapshotPublisher.current()
} }
@ -83,12 +92,21 @@ actor SessionManager {
self.snapshotPublisher.publish(self.compileSnapshot()) self.snapshotPublisher.publish(self.compileSnapshot())
} }
private func compileSnapshot() -> SessionSnapshot { private func validSessions() -> [Data: [Session.AddressType: Session]] {
let timestamp = Int32(Date().timeIntervalSince1970) let timestamp = Int32(Date().timeIntervalSince1970)
let sessions = self.sessions.compactMapValues { peerSessions in return self.sessions.compactMapValues { peerSessions in
let validSessions = peerSessions.filter { $0.value.lastTimestamp + self.ttl >= timestamp } let validSessions = peerSessions.filter { $0.value.lastTimestamp + self.ttl >= timestamp }
return validSessions.isEmpty ? nil : validSessions return validSessions.isEmpty ? nil : validSessions
} }
return SessionSnapshot(sessions: sessions) }
private func sessionCount() -> Int {
return self.sessions.values.reduce(0) { count, peerSessions in
count + peerSessions.count
}
}
private func compileSnapshot() -> SessionSnapshot {
return SessionSnapshot(sessions: self.validSessions())
} }
} }

View File

@ -1,3 +1,3 @@
#! /bin/sh #!/bin/sh
log stream --predicate 'subsystem == "com.jihe.punchnet.debug"' --info --style compact log stream --style compact --predicate 'subsystem == "com.jihe.punchnet.tun"'