Skip to content

Commit ae65c20

Browse files
authored
chore: upgrade Proto code to Swift 6 (#14)
Upgrades Proto to Swift 6, mostly by telling the compiler explicitly that things are sendable, but in some cases by scoping down our closures so they only capture sendable objects.
1 parent b41d364 commit ae65c20

File tree

5 files changed

+67
-39
lines changed

5 files changed

+67
-39
lines changed

Diff for: Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@
660660
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
661661
PRODUCT_NAME = "$(TARGET_NAME)";
662662
SWIFT_EMIT_LOC_STRINGS = YES;
663-
SWIFT_VERSION = 5.0;
663+
SWIFT_VERSION = 6.0;
664664
};
665665
name = Debug;
666666
};
@@ -690,7 +690,7 @@
690690
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
691691
PRODUCT_NAME = "$(TARGET_NAME)";
692692
SWIFT_EMIT_LOC_STRINGS = YES;
693-
SWIFT_VERSION = 5.0;
693+
SWIFT_VERSION = 6.0;
694694
};
695695
name = Release;
696696
};
@@ -835,7 +835,7 @@
835835
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests";
836836
PRODUCT_NAME = "$(TARGET_NAME)";
837837
SWIFT_EMIT_LOC_STRINGS = NO;
838-
SWIFT_VERSION = 5.0;
838+
SWIFT_VERSION = 6.0;
839839
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop";
840840
};
841841
name = Debug;
@@ -853,7 +853,7 @@
853853
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests";
854854
PRODUCT_NAME = "$(TARGET_NAME)";
855855
SWIFT_EMIT_LOC_STRINGS = NO;
856-
SWIFT_VERSION = 5.0;
856+
SWIFT_VERSION = 6.0;
857857
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop";
858858
};
859859
name = Release;

Diff for: Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/ProtoTests.xcscheme

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@
5454
savedToolIdentifier = ""
5555
useCustomWorkingDirectory = "NO"
5656
debugDocumentVersioning = "YES">
57+
<MacroExpansion>
58+
<BuildableReference
59+
BuildableIdentifier = "primary"
60+
BlueprintIdentifier = "961678FB2CFF100D00B2B6DF"
61+
BuildableName = "Coder Desktop.app"
62+
BlueprintName = "Coder Desktop"
63+
ReferencedContainer = "container:Coder Desktop.xcodeproj">
64+
</BuildableReference>
65+
</MacroExpansion>
5766
</ProfileAction>
5867
<AnalyzeAction
5968
buildConfiguration = "Debug">

Diff for: Coder Desktop/Proto/Receiver.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ actor Receiver<RecvMsg: Message> {
2222
dispatch.read(offset: 0, length: 4, queue: queue) { done, data, error in
2323
guard error == 0 else {
2424
let errStrPtr = strerror(error)
25-
let errStr = String(validatingUTF8: errStrPtr!)!
25+
let errStr = String(validatingCString: errStrPtr!)!
2626
continuation.resume(throwing: ReceiveError.readError(errStr))
2727
return
2828
}
@@ -42,7 +42,7 @@ actor Receiver<RecvMsg: Message> {
4242
dispatch.read(offset: 0, length: Int(length), queue: queue) { done, data, error in
4343
guard error == 0 else {
4444
let errStrPtr = strerror(error)
45-
let errStr = String(validatingUTF8: errStrPtr!)!
45+
let errStr = String(validatingCString: errStrPtr!)!
4646
continuation.resume(throwing: ReceiveError.readError(errStr))
4747
return
4848
}

Diff for: Coder Desktop/Proto/Speaker.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -133,20 +133,20 @@ class Speaker<SendMsg: RPCMessage & Message, RecvMsg: RPCMessage & Message> {
133133
/// Send a unary RPC message and handle the response
134134
func unaryRPC(_ req: SendMsg) async throws -> RecvMsg {
135135
return try await withCheckedThrowingContinuation { continuation in
136-
Task {
137-
let msgID = await self.secretary.record(continuation: continuation)
136+
Task { [sender, secretary, logger] in
137+
let msgID = await secretary.record(continuation: continuation)
138138
var req = req
139139
req.rpc = Vpn_RPC()
140140
req.rpc.msgID = msgID
141141
do {
142-
self.logger.debug("sending RPC with msgID: \(msgID)")
143-
try await self.sender.send(req)
142+
logger.debug("sending RPC with msgID: \(msgID)")
143+
try await sender.send(req)
144144
} catch {
145-
self.logger.warning("failed to send RPC with msgID: \(msgID): \(error)")
146-
await self.secretary.erase(id: req.rpc.msgID)
145+
logger.warning("failed to send RPC with msgID: \(msgID): \(error)")
146+
await secretary.erase(id: req.rpc.msgID)
147147
continuation.resume(throwing: error)
148148
}
149-
self.logger.debug("sent RPC with msgID: \(msgID)")
149+
logger.debug("sent RPC with msgID: \(msgID)")
150150
}
151151
}
152152
}
@@ -169,7 +169,7 @@ class Speaker<SendMsg: RPCMessage & Message, RecvMsg: RPCMessage & Message> {
169169
}
170170

171171
/// A class that performs the initial VPN protocol handshake and version negotiation.
172-
class Handshaker {
172+
class Handshaker: @unchecked Sendable {
173173
private let writeFD: FileHandle
174174
private let dispatch: DispatchIO
175175
private var theirData: Data = .init()
@@ -219,7 +219,7 @@ class Handshaker {
219219
private func handleRead(_: Bool, _ data: DispatchData?, _ error: Int32) {
220220
guard error == 0 else {
221221
let errStrPtr = strerror(error)
222-
let errStr = String(validatingUTF8: errStrPtr!)!
222+
let errStr = String(validatingCString: errStrPtr!)!
223223
continuation?.resume(throwing: HandshakeError.readError(errStr))
224224
return
225225
}
@@ -277,7 +277,7 @@ enum HandshakeError: Error {
277277
case unsupportedVersion([ProtoVersion])
278278
}
279279

280-
struct RPCRequest<SendMsg: RPCMessage & Message, RecvMsg: RPCMessage> {
280+
struct RPCRequest<SendMsg: RPCMessage & Message, RecvMsg: RPCMessage & Sendable>: Sendable {
281281
let msg: RecvMsg
282282
private let sender: Sender<SendMsg>
283283

@@ -302,7 +302,7 @@ enum RPCError: Error {
302302
}
303303

304304
/// An actor to record outgoing RPCs and route their replies to the original sender
305-
actor RPCSecretary<RecvMsg: RPCMessage> {
305+
actor RPCSecretary<RecvMsg: RPCMessage & Sendable> {
306306
private var continuations: [UInt64: CheckedContinuation<RecvMsg, Error>] = [:]
307307
private var nextMsgID: UInt64 = 1
308308

Diff for: Coder Desktop/ProtoTests/SpeakerTests.swift

+41-22
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,45 @@ import Testing
44

55
/// A concrete, test class for the abstract Speaker, which overrides the handlers to send things to
66
/// continuations we set in the test.
7-
class TestTunnel: Speaker<Vpn_TunnelMessage, Vpn_ManagerMessage> {
8-
var msgHandler: CheckedContinuation<Vpn_ManagerMessage, Error>?
7+
class TestTunnel: Speaker<Vpn_TunnelMessage, Vpn_ManagerMessage>, @unchecked Sendable {
8+
private var msgHandler: CheckedContinuation<Vpn_ManagerMessage, Error>?
99
override func handleMessage(_ msg: Vpn_ManagerMessage) {
1010
msgHandler?.resume(returning: msg)
1111
}
1212

13-
var rpcHandler: CheckedContinuation<RPCRequest<Vpn_TunnelMessage, Vpn_ManagerMessage>, Error>?
13+
/// Runs the given closure asynchronously and returns the next non-RPC message received.
14+
func expectMessage(with closure:
15+
@escaping @Sendable () async -> Void) async throws -> Vpn_ManagerMessage
16+
{
17+
return try await withCheckedThrowingContinuation { continuation in
18+
msgHandler = continuation
19+
Task {
20+
await closure()
21+
}
22+
}
23+
}
24+
25+
private var rpcHandler: CheckedContinuation<RPCRequest<Vpn_TunnelMessage, Vpn_ManagerMessage>, Error>?
1426
override func handleRPC(_ req: RPCRequest<Vpn_TunnelMessage, Vpn_ManagerMessage>) {
1527
rpcHandler?.resume(returning: req)
1628
}
29+
30+
/// Runs the given closure asynchronously and return the next non-RPC message received
31+
func expectRPC(with closure:
32+
@escaping @Sendable () async -> Void) async throws ->
33+
RPCRequest<Vpn_TunnelMessage, Vpn_ManagerMessage>
34+
{
35+
return try await withCheckedThrowingContinuation { continuation in
36+
rpcHandler = continuation
37+
Task {
38+
await closure()
39+
}
40+
}
41+
}
1742
}
1843

1944
@Suite(.timeLimit(.minutes(1)))
20-
struct SpeakerTests {
45+
struct SpeakerTests: Sendable {
2146
let pipeMT = Pipe()
2247
let pipeTM = Pipe()
2348
let uut: TestTunnel
@@ -56,14 +81,11 @@ struct SpeakerTests {
5681
@Test func handleSingleMessage() async throws {
5782
async let readDone: () = try uut.readLoop()
5883

59-
let got = try await withCheckedThrowingContinuation { continuation in
60-
uut.msgHandler = continuation
61-
Task {
62-
var s = Vpn_ManagerMessage()
63-
s.start = Vpn_StartRequest()
64-
await #expect(throws: Never.self) {
65-
try await sender.send(s)
66-
}
84+
let got = try await uut.expectMessage {
85+
var s = Vpn_ManagerMessage()
86+
s.start = Vpn_StartRequest()
87+
await #expect(throws: Never.self) {
88+
try await sender.send(s)
6789
}
6890
}
6991
#expect(got.msg == .start(Vpn_StartRequest()))
@@ -74,16 +96,13 @@ struct SpeakerTests {
7496
@Test func handleRPC() async throws {
7597
async let readDone: () = try uut.readLoop()
7698

77-
let got = try await withCheckedThrowingContinuation { continuation in
78-
uut.rpcHandler = continuation
79-
Task {
80-
var s = Vpn_ManagerMessage()
81-
s.start = Vpn_StartRequest()
82-
s.rpc = Vpn_RPC()
83-
s.rpc.msgID = 33
84-
await #expect(throws: Never.self) {
85-
try await sender.send(s)
86-
}
99+
let got = try await uut.expectRPC {
100+
var s = Vpn_ManagerMessage()
101+
s.start = Vpn_StartRequest()
102+
s.rpc = Vpn_RPC()
103+
s.rpc.msgID = 33
104+
await #expect(throws: Never.self) {
105+
try await sender.send(s)
87106
}
88107
}
89108
#expect(got.msg.msg == .start(Vpn_StartRequest()))

0 commit comments

Comments
 (0)