fix quicClient

This commit is contained in:
anlicheng 2026-04-28 14:18:08 +08:00
parent 337fa37b05
commit a70419e3e2
2 changed files with 161 additions and 50 deletions

View File

@ -168,8 +168,14 @@ actor SDLContextActor {
SDLLogger.log("[SDLContext] try start quicClient", for: .debug) SDLLogger.log("[SDLContext] try start quicClient", for: .debug)
let quicClient = try await self.startQUICClient() let quicClient = try await self.startQUICClient()
SDLLogger.log("[SDLContext] quicClient running!!!!") SDLLogger.log("[SDLContext] quicClient running!!!!")
await quicClient.waitClose() let exit = await quicClient.run()
SDLLogger.log("[SDLContext] quicClient closed!!!!") SDLLogger.log("[SDLContext] quicClient closed: \(exit)")
switch exit {
case .normal, .cancelled:
return
case .transportClosed, .readFailed, .writeFailed:
throw exit
}
} }
await self.supervisor.addWorker(name: "udpHole") { await self.supervisor.addWorker(name: "udpHole") {

View File

@ -20,6 +20,58 @@ enum SDLQUICError: Error {
case packetTooLarge case packetTooLarge
} }
enum SDLQUICClientExit: Error, Sendable, CustomStringConvertible {
case normal
case cancelled
case transportClosed(String)
case readFailed(String)
case writeFailed(String)
var description: String {
switch self {
case .normal:
return "normal"
case .cancelled:
return "cancelled"
case .transportClosed(let reason):
return "transportClosed(\(reason))"
case .readFailed(let reason):
return "readFailed(\(reason))"
case .writeFailed(let reason):
return "writeFailed(\(reason))"
}
}
}
private actor SDLQUICCloseWait {
private var exit: SDLQUICClientExit?
private var waiters: [CheckedContinuation<SDLQUICClientExit, Never>] = []
func wait() async -> SDLQUICClientExit {
if let exit {
return exit
}
return await withCheckedContinuation { continuation in
waiters.append(continuation)
}
}
func close(_ exit: SDLQUICClientExit) {
guard self.exit == nil else {
return
}
self.exit = exit
let waiters = self.waiters
self.waiters.removeAll()
for waiter in waiters {
waiter.resume(returning: exit)
}
}
}
final class SDLQUICClient { final class SDLQUICClient {
private let allocator = ByteBufferAllocator() private let allocator = ByteBufferAllocator()
// 64K // 64K
@ -35,7 +87,7 @@ final class SDLQUICClient {
private let connection: NWConnection private let connection: NWConnection
private let queue = DispatchQueue(label: "com.sdl.QUICClient.queue") // 线 private let queue = DispatchQueue(label: "com.sdl.QUICClient.queue") // 线
private let (closeStream, closeCont) = AsyncStream.makeStream(of: Void.self) private let closeWait = SDLQUICCloseWait()
private let (readyStream, readyCont) = AsyncStream.makeStream(of: Void.self) private let (readyStream, readyCont) = AsyncStream.makeStream(of: Void.self)
init(host: String, port: UInt16, maxPacketSize: Int = 64 * 1024, maxBufferSize: Int = 2 * 1024 * 1024) { init(host: String, port: UInt16, maxPacketSize: Int = 64 * 1024, maxBufferSize: Int = 2 * 1024 * 1024) {
@ -66,60 +118,50 @@ final class SDLQUICClient {
case .ready: case .ready:
self.readyCont.yield() self.readyCont.yield()
self.readyCont.finish() self.readyCont.finish()
case .failed(_), .cancelled: case .failed(let error):
self.closeCont.yield() self.readyCont.finish()
self.closeCont.finish() Task {
await self.close(.transportClosed("failed: \(error)"))
}
case .cancelled:
self.readyCont.finish()
Task {
await self.close(.cancelled)
}
default: default:
() ()
} }
} }
connection.start(queue: self.queue) connection.start(queue: self.queue)
}
// func run() async -> SDLQUICClientExit {
self.readTask = Task { await withTaskCancellationHandler {
var buffer = allocator.buffer(capacity: self.maxBufferSize) await withTaskGroup(of: SDLQUICClientExit.self) { group in
let threshold = self.maxBufferSize / 10 * 6 group.addTask {
do { await self.readLoop()
while !Task.isCancelled {
let (isComplete, data) = try await self.readOnce()
if let data, !data.isEmpty {
buffer.writeBytes(data)
let frames = try parseFrames(buffer: &buffer)
if buffer.readerIndex > threshold {
buffer.discardReadBytes()
}
for frame in frames {
if let message = decode(frame: frame) {
self.messageCont.yield(message)
}
}
}
if isComplete {
break
}
} }
group.addTask {
await self.heartbeatLoop()
}
group.addTask {
await self.waitClose()
}
let exit = await group.next() ?? .normal
group.cancelAll()
self.connection.cancel()
self.messageCont.finish() self.messageCont.finish()
} catch { await self.close(exit)
return exit
}
} onCancel: {
Task {
await self.close(.cancelled)
self.connection.cancel()
self.messageCont.finish() self.messageCont.finish()
} }
} }
//
self.pingTask = Task {
let timerStream = SDLAsyncTimerStream()
timerStream.start(interval: .seconds(5))
for await _ in timerStream.stream {
if Task.isCancelled {
break
}
self.send(type: .ping, data: Data())
}
SDLLogger.log("[SDLQUICClient] udp pingTask cancel", for: .debug)
}
} }
func send(type: SDLPacketType, data: Data) { func send(type: SDLPacketType, data: Data) {
@ -132,22 +174,85 @@ final class SDLQUICClient {
connection.send(content: packet, completion: .contentProcessed { error in connection.send(content: packet, completion: .contentProcessed { error in
if let error { if let error {
SDLLogger.log("[SDLQUICClient] send data get error: \(error)", for: .debug) SDLLogger.log("[SDLQUICClient] send data get error: \(error)", for: .debug)
Task {
await self.close(.writeFailed("\(error)"))
}
} }
}) })
} }
func waitReady() async throws { func waitReady() async throws {
for await _ in readyStream {} for await _ in readyStream {
return
}
let exit = await closeWait.wait()
throw exit
} }
func waitClose() async { func waitClose() async -> SDLQUICClientExit {
for await _ in closeStream {} await closeWait.wait()
} }
func stop() { func stop() {
self.connection.cancel() self.connection.cancel()
} }
func close(_ exit: SDLQUICClientExit = .normal) async {
await closeWait.close(exit)
}
private func readLoop() async -> SDLQUICClientExit {
var buffer = allocator.buffer(capacity: self.maxBufferSize)
let threshold = self.maxBufferSize / 10 * 6
defer {
self.messageCont.finish()
}
do {
while !Task.isCancelled {
let (isComplete, data) = try await self.readOnce()
if let data, !data.isEmpty {
buffer.writeBytes(data)
let frames = try parseFrames(buffer: &buffer)
if buffer.readerIndex > threshold {
buffer.discardReadBytes()
}
for frame in frames {
if let message = decode(frame: frame) {
self.messageCont.yield(message)
}
}
}
if isComplete {
return .transportClosed("receive complete")
}
}
return .cancelled
} catch is CancellationError {
return .cancelled
} catch {
return .readFailed("\(error)")
}
}
private func heartbeatLoop() async -> SDLQUICClientExit {
let timerStream = SDLAsyncTimerStream()
timerStream.start(interval: .seconds(5))
for await _ in timerStream.stream {
if Task.isCancelled {
break
}
self.send(type: .ping, data: Data())
}
SDLLogger.log("[SDLQUICClient] udp pingTask cancel", for: .debug)
return .cancelled
}
// //
private func parseFrames(buffer: inout ByteBuffer) throws -> [ByteBuffer] { private func parseFrames(buffer: inout ByteBuffer) throws -> [ByteBuffer] {
guard buffer.readableBytes >= 2 else { guard buffer.readableBytes >= 2 else {