This commit is contained in:
anlicheng 2026-05-28 17:15:04 +08:00
parent 47e45bbec4
commit 3217614f02
4 changed files with 74 additions and 14 deletions

View File

@ -62,6 +62,8 @@ actor SDLContextActor {
// //
private let policyService: PolicyService private let policyService: PolicyService
private var rootTask: Task<Void, Error>? private var rootTask: Task<Void, Error>?
private var rootTaskID: UUID?
private var terminalError: Error?
private let readySignal = AsyncOneShot<Void>() private let readySignal = AsyncOneShot<Void>()
public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher) { public init(provider: NEPacketTunnelProvider, config: SDLConfiguration, rsaCipher: RSACipher) {
@ -140,21 +142,31 @@ actor SDLContextActor {
return return
} }
let rootTaskID = UUID()
let rootTask = Task { let rootTask = Task {
var result: Result<Void, Error> = .success(())
do { do {
try await self.runRootBody() try await self.runRootBody()
await self.cleanupRoot()
} catch is CancellationError { } catch is CancellationError {
SDLLogger.fatal("[SDLContext] root task cancelled", category: .context) if let terminalError = await self.consumeTerminalError() {
await self.cleanupRoot() SDLLogger.fatal("[SDLContext] root task stopped by terminal error: \(terminalError)", category: .context)
throw CancellationError() result = .failure(terminalError)
} else {
SDLLogger.fatal("[SDLContext] root task cancelled", category: .context)
result = .failure(CancellationError())
}
} catch { } catch {
SDLLogger.fatal("[SDLContext] root task failed: \(error)", category: .context) SDLLogger.fatal("[SDLContext] root task failed: \(error)", category: .context)
await self.readySignal.fail(error) await self.readySignal.fail(error)
await self.cleanupRoot() result = .failure(error)
throw error
} }
await self.cleanupRoot()
await self.finishRootTask(id: rootTaskID)
try result.get()
} }
self.rootTaskID = rootTaskID
self.rootTask = rootTask self.rootTask = rootTask
do { do {
@ -164,6 +176,8 @@ actor SDLContextActor {
rootTask.cancel() rootTask.cancel()
_ = try? await rootTask.value _ = try? await rootTask.value
self.rootTask = nil self.rootTask = nil
self.rootTaskID = nil
self.terminalError = nil
throw error throw error
} }
} }
@ -172,13 +186,14 @@ actor SDLContextActor {
public func stop() async { public func stop() async {
SDLLogger.fatal("[SDLContext] stop requested", category: .context) SDLLogger.fatal("[SDLContext] stop requested", category: .context)
let rootTask = self.rootTask let rootTask = self.rootTask
self.rootTask = nil
rootTask?.cancel() rootTask?.cancel()
await self.readySignal.fail(CancellationError()) await self.readySignal.fail(CancellationError())
_ = try? await rootTask?.value _ = try? await rootTask?.value
await self.cleanupRoot() self.rootTask = nil
self.rootTaskID = nil
self.terminalError = nil
} }
private func runRootBody() async throws { private func runRootBody() async throws {
@ -370,6 +385,28 @@ actor SDLContextActor {
await self.stopCurrentIPv6AssistClient() await self.stopCurrentIPv6AssistClient()
} }
private func requestRootShutdown(error: Error) async {
self.terminalError = error
await self.readySignal.fail(error)
self.rootTask?.cancel()
}
private func consumeTerminalError() -> Error? {
let error = self.terminalError
self.terminalError = nil
return error
}
private func finishRootTask(id: UUID) {
guard self.rootTaskID == id else {
return
}
self.rootTask = nil
self.rootTaskID = nil
self.terminalError = nil
}
deinit { deinit {
SDLLogger.log("[SDLContext] deinit", category: .context) SDLLogger.log("[SDLContext] deinit", category: .context)
} }
@ -441,7 +478,7 @@ extension SDLContextActor {
SDLLogger.fatal("[SDLContext] Super shutdown received: \(message)", category: .context) SDLLogger.fatal("[SDLContext] Super shutdown received: \(message)", category: .context)
self.publishTunnelEvent(message: message) self.publishTunnelEvent(message: message)
let error = NSError(domain: "com.jihe.punchnet.tun", code: -2) let error = NSError(domain: "com.jihe.punchnet.tun", code: -2)
self.provider.cancelTunnelWithError(error) await self.failTunnel(error)
case .applyPolicyResponse(let policyResponse): case .applyPolicyResponse(let policyResponse):
await self.policyService.applyPolicyResponse(policyResponse) await self.policyService.applyPolicyResponse(policyResponse)
case .applyExposedServiceResponse(let response): case .applyExposedServiceResponse(let response):
@ -469,8 +506,8 @@ extension SDLContextActor {
private func failTunnel(_ error: Error) async { private func failTunnel(_ error: Error) async {
SDLLogger.fatal("[SDLContext] failTunnel: \(error)", category: .context) SDLLogger.fatal("[SDLContext] failTunnel: \(error)", category: .context)
await self.readySignal.fail(error)
self.provider.cancelTunnelWithError(error) self.provider.cancelTunnelWithError(error)
await self.requestRootShutdown(error: error)
} }
private func requestExposedService() async { private func requestExposedService() async {

View File

@ -82,8 +82,15 @@ actor DNSCloudClient {
} }
/// DNS TUN IP /// DNS TUN IP
func forward(ipPacketData: Data) { func forward(ipPacketData: Data) async {
guard connection.state == .ready else { do {
try await self.readySignal.wait(timeout: .seconds(3))
} catch {
SDLLogger.log("[DNSCloudClient] drop query before ready: \(error)", category: .dns)
return
}
guard !self.isStopped, connection.state == .ready else {
return return
} }

View File

@ -104,8 +104,19 @@ actor DNSLocalClient {
} }
} }
func query(tracker: DNSTracker, dnsPayload: Data) { func query(tracker: DNSTracker, dnsPayload: Data) async {
guard connection.state == .ready, dnsPayload.count >= 2 else { guard dnsPayload.count >= 2 else {
return
}
do {
try await self.readySignal.wait(timeout: .seconds(3))
} catch {
SDLLogger.log("[DNSLocalClient] drop query before ready: \(error)", category: .dns)
return
}
guard !self.isStopped, connection.state == .ready else {
return return
} }
@ -146,6 +157,10 @@ actor DNSLocalClient {
private func handleConnectionStateUpdate(_ state: NWConnection.State) async { private func handleConnectionStateUpdate(_ state: NWConnection.State) async {
switch state { switch state {
case .ready: case .ready:
guard !self.isStopped else {
return
}
await self.readySignal.succeed(()) await self.readySignal.succeed(())
case .failed(let error): case .failed(let error):
await self.readySignal.fail(DNSLocalError.failed(error)) await self.readySignal.fail(DNSLocalError.failed(error))

View File

@ -44,6 +44,7 @@ struct FlowSession: Hashable {
// MARK: - // MARK: -
final class FlowSessionTable: @unchecked Sendable { final class FlowSessionTable: @unchecked Sendable {
private enum SessionState: Hashable { private enum SessionState: Hashable {
case tcpPending case tcpPending
case tcpEstablished case tcpEstablished