Skip to content

Commit c041679

Browse files
authored
Add concurrency annotations to fix strict compilation warnings (#155)
This contains fixes for warnings that appear by enabling Swift strict concurrency in the Xcode 15 beta: ``` swiftSettings: [.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"])] ```
1 parent 2f622ba commit c041679

File tree

57 files changed

+412
-323
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+412
-323
lines changed

.swiftlint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ custom_rules:
114114
newline_before_brace:
115115
name: "Closing braces shouldn't have empty lines before them"
116116
regex: '\n\n\}'
117+
sendable_order:
118+
name: "@escaping should precede @Sendable when used together"
119+
regex: '@Sendable\s+@escaping'
117120
space_before_comma:
118121
name: "Commas should never have a space before them"
119122
regex: '\s+,'

Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SwiftProtobuf
1313
/// superficiality of human-computer communication. DOCTOR simulates a
1414
/// psychotherapist, and is commonly found as an Easter egg in emacs
1515
/// distributions.
16-
internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface {
16+
internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface: Sendable {
1717

1818
/// Say is a unary RPC. Eliza responds to the prompt with a single sentence.
1919
@available(iOS 13, *)
@@ -32,7 +32,7 @@ internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface {
3232
}
3333

3434
/// Concrete implementation of `Connectrpc_Eliza_V1_ElizaServiceClientInterface`.
35-
internal final class Connectrpc_Eliza_V1_ElizaServiceClient: Connectrpc_Eliza_V1_ElizaServiceClientInterface {
35+
internal final class Connectrpc_Eliza_V1_ElizaServiceClient: Connectrpc_Eliza_V1_ElizaServiceClientInterface, Sendable {
3636
private let client: Connect.ProtocolClientInterface
3737

3838
internal init(client: Connect.ProtocolClientInterface) {

Libraries/Connect/Implementation/Codecs/JSONCodec.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
// limitations under the License.
1414

1515
import Foundation
16-
import SwiftProtobuf
16+
// TODO: Remove `@preconcurrency` once `SwiftProtobuf.JSON{Encoding|Decoding}Options` are `Sendable`
17+
@preconcurrency import SwiftProtobuf
1718

1819
/// Codec providing functionality for serializing to/from JSON.
19-
public struct JSONCodec {
20+
public struct JSONCodec: Sendable {
2021
private let encodingOptions: JSONEncodingOptions
2122
private let decodingOptions: JSONDecodingOptions = {
2223
var options = JSONDecodingOptions()
@@ -43,11 +44,11 @@ extension JSONCodec: Codec {
4344
return "json"
4445
}
4546

46-
public func serialize<Input: SwiftProtobuf.Message>(message: Input) throws -> Data {
47+
public func serialize<Input: ProtobufMessage>(message: Input) throws -> Data {
4748
return try message.jsonUTF8Data(options: self.encodingOptions)
4849
}
4950

50-
public func deserialize<Output: SwiftProtobuf.Message>(source: Data) throws -> Output {
51+
public func deserialize<Output: ProtobufMessage>(source: Data) throws -> Output {
5152
return try Output(jsonUTF8Data: source, options: self.decodingOptions)
5253
}
5354
}

Libraries/Connect/Implementation/Codecs/ProtoCodec.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ extension ProtoCodec: Codec {
2525
return "proto"
2626
}
2727

28-
public func serialize<Input: SwiftProtobuf.Message>(message: Input) throws -> Data {
28+
public func serialize<Input: ProtobufMessage>(message: Input) throws -> Data {
2929
return try message.serializedData()
3030
}
3131

32-
public func deserialize<Output: SwiftProtobuf.Message>(source: Data) throws -> Output {
32+
public func deserialize<Output: ProtobufMessage>(source: Data) throws -> Output {
3333
return try Output(serializedData: source)
3434
}
3535
}

Libraries/Connect/Implementation/Interceptors/ConnectInterceptor.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ extension ConnectInterceptor: Interceptor {
101101
}
102102

103103
func streamFunction() -> StreamFunction {
104-
var responseHeaders: Headers?
104+
let responseHeaders = Locked<Headers?>(nil)
105105
return StreamFunction(
106106
requestFunction: { request in
107107
var headers = request.headers
@@ -124,12 +124,12 @@ extension ConnectInterceptor: Interceptor {
124124
streamResultFunction: { result in
125125
switch result {
126126
case .headers(let headers):
127-
responseHeaders = headers
127+
responseHeaders.value = headers
128128
return result
129129

130130
case .message(let data):
131131
do {
132-
let responseCompressionPool = responseHeaders?[
132+
let responseCompressionPool = responseHeaders.value?[
133133
HeaderConstants.connectStreamingContentEncoding
134134
]?.first.flatMap { self.config.responseCompressionPool(forName: $0) }
135135
let (headerByte, message) = try Envelope.unpackMessage(
@@ -161,7 +161,7 @@ extension ConnectInterceptor: Interceptor {
161161
code: code,
162162
error: ConnectError.from(
163163
code: code,
164-
headers: responseHeaders ?? [:],
164+
headers: responseHeaders.value ?? [:],
165165
source: nil
166166
),
167167
trailers: trailers

Libraries/Connect/Implementation/Interceptors/GRPCWebInterceptor.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ extension GRPCWebInterceptor: Interceptor {
111111
}
112112

113113
func streamFunction() -> StreamFunction {
114-
var responseHeaders: Headers?
114+
let responseHeaders = Locked<Headers?>(nil)
115115
return StreamFunction(
116116
requestFunction: { request in
117117
return HTTPRequest(
@@ -137,13 +137,13 @@ extension GRPCWebInterceptor: Interceptor {
137137
trailers: headers
138138
)
139139
} else {
140-
responseHeaders = headers
140+
responseHeaders.value = headers
141141
return result
142142
}
143143

144144
case .message(let data):
145145
do {
146-
let responseCompressionPool = responseHeaders?[
146+
let responseCompressionPool = responseHeaders.value?[
147147
HeaderConstants.grpcContentEncoding
148148
]?.first.flatMap { self.config.responseCompressionPool(forName: $0) }
149149
let (headerByte, unpackedData) = try Envelope.unpackMessage(

Libraries/Connect/Implementation/Interceptors/InterceptorChain.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
/// Represents a chain of interceptors that is used for a single request/stream,
1616
/// and orchestrates invoking each of them in the proper order.
17-
struct InterceptorChain {
17+
struct InterceptorChain: Sendable {
1818
private let interceptors: [Interceptor]
1919

2020
/// Initialize the interceptor chain.
@@ -23,9 +23,7 @@ struct InterceptorChain {
2323
///
2424
/// - parameter interceptors: Closures that should be called to create interceptors.
2525
/// - parameter config: Config to use for setting up interceptors.
26-
init(
27-
interceptors: [(ProtocolClientConfig) -> Interceptor], config: ProtocolClientConfig
28-
) {
26+
init(interceptors: [InterceptorInitializer], config: ProtocolClientConfig) {
2927
self.interceptors = interceptors.map { initialize in initialize(config) }
3028
}
3129

@@ -85,7 +83,7 @@ struct InterceptorChain {
8583
}
8684
}
8785

88-
private func executeInterceptors<T>(_ interceptors: [(T) -> T], initial: T) -> T {
86+
private func executeInterceptors<T>(_ interceptors: [@Sendable (T) -> T], initial: T) -> T {
8987
var next = initial
9088
for interceptor in interceptors {
9189
next = interceptor(next)

Libraries/Connect/Implementation/Lock.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import Foundation
1616

1717
/// Internal implementation of a lock. Wraps usage of `os_unfair_lock`.
18-
final class Lock {
18+
final class Lock: Sendable {
1919
private let underlyingLock: UnsafeMutablePointer<os_unfair_lock>
2020

2121
init() {
@@ -24,6 +24,11 @@ final class Lock {
2424
self.underlyingLock.initialize(to: os_unfair_lock())
2525
}
2626

27+
/// Perform an action within the context of the lock.
28+
///
29+
/// - parameter action: Closure to be executed in the context of the lock.
30+
///
31+
/// - returns: The result of the closure.
2732
func perform<T>(action: @escaping () -> T) -> T {
2833
os_unfair_lock_lock(self.underlyingLock)
2934
defer { os_unfair_lock_unlock(self.underlyingLock) }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2022-2023 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
/// Class containing an internal lock which can be used to ensure thread-safe access to an
18+
/// underlying value. Conforms to `Sendable`, making it accessible from `@Sendable` closures.
19+
public final class Locked<T>: @unchecked Sendable {
20+
private let lock = Lock()
21+
private var wrappedValue: T
22+
23+
/// Thread-safe access to the underlying value.
24+
public var value: T {
25+
get { self.lock.perform { self.wrappedValue } }
26+
set { self.lock.perform { self.wrappedValue = newValue } }
27+
}
28+
29+
/// Perform an action with the underlying value, potentially updating that value.
30+
///
31+
/// - parameter action: Closure to perform with the underlying value.
32+
public func perform(action: @escaping (inout T) -> Void) {
33+
self.lock.perform {
34+
action(&self.wrappedValue)
35+
}
36+
}
37+
38+
public init(_ value: T) {
39+
self.wrappedValue = value
40+
}
41+
}

0 commit comments

Comments
 (0)