diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj
index 223b83f..e5ae28b 100644
--- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj
+++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj
@@ -660,7 +660,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = Debug;
};
@@ -690,7 +690,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = Release;
};
@@ -835,7 +835,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop";
};
name = Debug;
@@ -853,7 +853,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop";
};
name = Release;
diff --git a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme
index 3366121..556492f 100644
--- a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme
+++ b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme
@@ -54,6 +54,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
+
+
+
+
diff --git a/Coder Desktop/Proto/Receiver.swift b/Coder Desktop/Proto/Receiver.swift
index 74a85cb..6a279e6 100644
--- a/Coder Desktop/Proto/Receiver.swift
+++ b/Coder Desktop/Proto/Receiver.swift
@@ -22,7 +22,7 @@ actor Receiver {
dispatch.read(offset: 0, length: 4, queue: queue) { done, data, error in
guard error == 0 else {
let errStrPtr = strerror(error)
- let errStr = String(validatingUTF8: errStrPtr!)!
+ let errStr = String(validatingCString: errStrPtr!)!
continuation.resume(throwing: ReceiveError.readError(errStr))
return
}
@@ -42,7 +42,7 @@ actor Receiver {
dispatch.read(offset: 0, length: Int(length), queue: queue) { done, data, error in
guard error == 0 else {
let errStrPtr = strerror(error)
- let errStr = String(validatingUTF8: errStrPtr!)!
+ let errStr = String(validatingCString: errStrPtr!)!
continuation.resume(throwing: ReceiveError.readError(errStr))
return
}
diff --git a/Coder Desktop/Proto/Speaker.swift b/Coder Desktop/Proto/Speaker.swift
index fdb334b..ca0740d 100644
--- a/Coder Desktop/Proto/Speaker.swift
+++ b/Coder Desktop/Proto/Speaker.swift
@@ -133,20 +133,20 @@ class Speaker {
/// Send a unary RPC message and handle the response
func unaryRPC(_ req: SendMsg) async throws -> RecvMsg {
return try await withCheckedThrowingContinuation { continuation in
- Task {
- let msgID = await self.secretary.record(continuation: continuation)
+ Task { [sender, secretary, logger] in
+ let msgID = await secretary.record(continuation: continuation)
var req = req
req.rpc = Vpn_RPC()
req.rpc.msgID = msgID
do {
- self.logger.debug("sending RPC with msgID: \(msgID)")
- try await self.sender.send(req)
+ logger.debug("sending RPC with msgID: \(msgID)")
+ try await sender.send(req)
} catch {
- self.logger.warning("failed to send RPC with msgID: \(msgID): \(error)")
- await self.secretary.erase(id: req.rpc.msgID)
+ logger.warning("failed to send RPC with msgID: \(msgID): \(error)")
+ await secretary.erase(id: req.rpc.msgID)
continuation.resume(throwing: error)
}
- self.logger.debug("sent RPC with msgID: \(msgID)")
+ logger.debug("sent RPC with msgID: \(msgID)")
}
}
}
@@ -169,7 +169,7 @@ class Speaker {
}
/// A class that performs the initial VPN protocol handshake and version negotiation.
-class Handshaker {
+class Handshaker: @unchecked Sendable {
private let writeFD: FileHandle
private let dispatch: DispatchIO
private var theirData: Data = .init()
@@ -219,7 +219,7 @@ class Handshaker {
private func handleRead(_: Bool, _ data: DispatchData?, _ error: Int32) {
guard error == 0 else {
let errStrPtr = strerror(error)
- let errStr = String(validatingUTF8: errStrPtr!)!
+ let errStr = String(validatingCString: errStrPtr!)!
continuation?.resume(throwing: HandshakeError.readError(errStr))
return
}
@@ -277,7 +277,7 @@ enum HandshakeError: Error {
case unsupportedVersion([ProtoVersion])
}
-struct RPCRequest {
+struct RPCRequest: Sendable {
let msg: RecvMsg
private let sender: Sender
@@ -302,7 +302,7 @@ enum RPCError: Error {
}
/// An actor to record outgoing RPCs and route their replies to the original sender
-actor RPCSecretary {
+actor RPCSecretary {
private var continuations: [UInt64: CheckedContinuation] = [:]
private var nextMsgID: UInt64 = 1
diff --git a/Coder Desktop/ProtoTests/SpeakerTests.swift b/Coder Desktop/ProtoTests/SpeakerTests.swift
index 08829ff..82651cb 100644
--- a/Coder Desktop/ProtoTests/SpeakerTests.swift
+++ b/Coder Desktop/ProtoTests/SpeakerTests.swift
@@ -4,20 +4,45 @@ import Testing
/// A concrete, test class for the abstract Speaker, which overrides the handlers to send things to
/// continuations we set in the test.
-class TestTunnel: Speaker {
- var msgHandler: CheckedContinuation?
+class TestTunnel: Speaker, @unchecked Sendable {
+ private var msgHandler: CheckedContinuation?
override func handleMessage(_ msg: Vpn_ManagerMessage) {
msgHandler?.resume(returning: msg)
}
- var rpcHandler: CheckedContinuation, Error>?
+ /// Runs the given closure asynchronously and returns the next non-RPC message received.
+ func expectMessage(with closure:
+ @escaping @Sendable () async -> Void) async throws -> Vpn_ManagerMessage
+ {
+ return try await withCheckedThrowingContinuation { continuation in
+ msgHandler = continuation
+ Task {
+ await closure()
+ }
+ }
+ }
+
+ private var rpcHandler: CheckedContinuation, Error>?
override func handleRPC(_ req: RPCRequest) {
rpcHandler?.resume(returning: req)
}
+
+ /// Runs the given closure asynchronously and return the next non-RPC message received
+ func expectRPC(with closure:
+ @escaping @Sendable () async -> Void) async throws ->
+ RPCRequest
+ {
+ return try await withCheckedThrowingContinuation { continuation in
+ rpcHandler = continuation
+ Task {
+ await closure()
+ }
+ }
+ }
}
@Suite(.timeLimit(.minutes(1)))
-struct SpeakerTests {
+struct SpeakerTests: Sendable {
let pipeMT = Pipe()
let pipeTM = Pipe()
let uut: TestTunnel
@@ -56,14 +81,11 @@ struct SpeakerTests {
@Test func handleSingleMessage() async throws {
async let readDone: () = try uut.readLoop()
- let got = try await withCheckedThrowingContinuation { continuation in
- uut.msgHandler = continuation
- Task {
- var s = Vpn_ManagerMessage()
- s.start = Vpn_StartRequest()
- await #expect(throws: Never.self) {
- try await sender.send(s)
- }
+ let got = try await uut.expectMessage {
+ var s = Vpn_ManagerMessage()
+ s.start = Vpn_StartRequest()
+ await #expect(throws: Never.self) {
+ try await sender.send(s)
}
}
#expect(got.msg == .start(Vpn_StartRequest()))
@@ -74,16 +96,13 @@ struct SpeakerTests {
@Test func handleRPC() async throws {
async let readDone: () = try uut.readLoop()
- let got = try await withCheckedThrowingContinuation { continuation in
- uut.rpcHandler = continuation
- Task {
- var s = Vpn_ManagerMessage()
- s.start = Vpn_StartRequest()
- s.rpc = Vpn_RPC()
- s.rpc.msgID = 33
- await #expect(throws: Never.self) {
- try await sender.send(s)
- }
+ let got = try await uut.expectRPC {
+ var s = Vpn_ManagerMessage()
+ s.start = Vpn_StartRequest()
+ s.rpc = Vpn_RPC()
+ s.rpc.msgID = 33
+ await #expect(throws: Never.self) {
+ try await sender.send(s)
}
}
#expect(got.msg.msg == .start(Vpn_StartRequest()))