Skip to content

Commit 9d68108

Browse files
committed
Add Sendable conformance to core async/await API
Motivation: The core async/await API should have appropriate 'Sendable' conformance before we publish it to main. This change adds it to the core parts of the new API. There are still numerous types which can be made 'Sendable' but are either less significant (e.g. configuration) or require depdendencies to adopt 'Sendable' first (and would otherwise require `@preconcurrency`) Modifications: - Require swift-protobuf to be at least 1.19.0 (as 1.19.0 introduced `Sendable` to generated messages) - Regenerate examples etc. - Add `GRPCSendable` to ease adoption - Make some plain-old-data types `Sendable` including `GRPCStatus` - Require `Request` and `Response` to be `Sendable` in the client and server async APIs - Make the async server context `@unchecked Sendable` - Make the async writer and stream reader `Sendable` Result: Core async API is `Sendable`.
1 parent e933e79 commit 9d68108

17 files changed

+108
-52
lines changed

Sources/GRPC/AsyncAwaitSupport/AsyncWriter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import NIOCore
2727
/// may suspend if the writer has been paused.
2828
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
2929
@usableFromInline
30-
internal final actor AsyncWriter<Delegate: AsyncWriterDelegate> {
30+
internal final actor AsyncWriter<Delegate: AsyncWriterDelegate>: Sendable {
3131
@usableFromInline
3232
internal typealias Element = Delegate.Element
3333

@@ -36,7 +36,7 @@ internal final actor AsyncWriter<Delegate: AsyncWriterDelegate> {
3636

3737
/// A value pending a write.
3838
@usableFromInline
39-
internal struct _Pending<Value> {
39+
internal struct _Pending<Value: Sendable>: Sendable {
4040
@usableFromInline
4141
var value: Value
4242

@@ -323,9 +323,9 @@ public struct GRPCAsyncWriterError: Error, Hashable {
323323
}
324324

325325
@usableFromInline
326-
internal protocol AsyncWriterDelegate: AnyObject {
327-
associatedtype Element
328-
associatedtype End
326+
internal protocol AsyncWriterDelegate: AnyObject, Sendable {
327+
associatedtype Element: Sendable
328+
associatedtype End: Sendable
329329

330330
@inlinable
331331
func write(_ element: Element)

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncBidirectionalStreamingCall.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import NIOHPACK
1919

2020
/// Async-await variant of BidirectionalStreamingCall.
2121
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
22-
public struct GRPCAsyncBidirectionalStreamingCall<Request, Response> {
22+
public struct GRPCAsyncBidirectionalStreamingCall<Request: Sendable, Response: Sendable> {
2323
private let call: Call<Request, Response>
2424
private let responseParts: StreamingResponseParts<Response>
2525
private let responseSource: PassthroughMessageSource<Response, Error>

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncClientStreamingCall.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import NIOHPACK
1919

2020
/// Async-await variant of `ClientStreamingCall`.
2121
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
22-
public struct GRPCAsyncClientStreamingCall<Request, Response> {
22+
public struct GRPCAsyncClientStreamingCall<Request: Sendable, Response: Sendable> {
2323
private let call: Call<Request, Response>
2424
private let responseParts: UnaryResponseParts<Response>
2525

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncRequestStream.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/// This is currently a wrapper around AsyncThrowingStream because we want to be
2020
/// able to swap out the implementation for something else in the future.
2121
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
22-
public struct GRPCAsyncRequestStream<Element>: AsyncSequence {
22+
public struct GRPCAsyncRequestStream<Element: Sendable>: AsyncSequence {
2323
@usableFromInline
2424
internal typealias _WrappedStream = PassthroughMessageSequence<Element, Error>
2525

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncRequestStreamWriter.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
/// try await stream.finish()
3232
/// ```
3333
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
34-
public struct GRPCAsyncRequestStreamWriter<Request> {
34+
public struct GRPCAsyncRequestStreamWriter<Request: Sendable> {
3535
@usableFromInline
3636
internal let asyncWriter: AsyncWriter<Delegate<Request>>
3737

@@ -78,7 +78,7 @@ public struct GRPCAsyncRequestStreamWriter<Request> {
7878
extension GRPCAsyncRequestStreamWriter {
7979
/// A delegate for the writer which writes messages to an underlying receiver.`
8080
@usableFromInline
81-
internal final class Delegate<Request>: AsyncWriterDelegate {
81+
internal final class Delegate<Request: Sendable>: AsyncWriterDelegate, Sendable {
8282
@usableFromInline
8383
internal typealias Element = (Request, Compression)
8484

@@ -89,16 +89,16 @@ extension GRPCAsyncRequestStreamWriter {
8989
internal let _compressionEnabled: Bool
9090

9191
@usableFromInline
92-
internal let _send: (Request, MessageMetadata) -> Void
92+
internal let _send: @Sendable(Request, MessageMetadata) -> Void
9393

9494
@usableFromInline
95-
internal let _finish: () -> Void
95+
internal let _finish: @Sendable() -> Void
9696

9797
@inlinable
9898
internal init(
9999
compressionEnabled: Bool,
100-
send: @escaping (Request, MessageMetadata) -> Void,
101-
finish: @escaping () -> Void
100+
send: @Sendable @escaping (Request, MessageMetadata) -> Void,
101+
finish: @Sendable @escaping () -> Void
102102
) {
103103
self._compressionEnabled = compressionEnabled
104104
self._send = send

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncResponseStreamWriter.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
/// Writer for server-streaming RPC handlers to provide responses.
2020
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
21-
public struct GRPCAsyncResponseStreamWriter<Response> {
21+
public struct GRPCAsyncResponseStreamWriter<Response: Sendable> {
2222
@usableFromInline
2323
internal typealias Element = (Response, Compression)
2424

@@ -44,7 +44,7 @@ public struct GRPCAsyncResponseStreamWriter<Response> {
4444

4545
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
4646
@usableFromInline
47-
internal final class AsyncResponseStreamWriterDelegate<Response>: AsyncWriterDelegate {
47+
internal final class AsyncResponseStreamWriterDelegate<Response: Sendable>: AsyncWriterDelegate {
4848
@usableFromInline
4949
internal typealias Element = (Response, Compression)
5050

@@ -55,10 +55,10 @@ internal final class AsyncResponseStreamWriterDelegate<Response>: AsyncWriterDel
5555
internal let _context: GRPCAsyncServerCallContext
5656

5757
@usableFromInline
58-
internal let _send: (Response, MessageMetadata) -> Void
58+
internal let _send: @Sendable(Response, MessageMetadata) -> Void
5959

6060
@usableFromInline
61-
internal let _finish: (GRPCStatus) -> Void
61+
internal let _finish: @Sendable(GRPCStatus) -> Void
6262

6363
@usableFromInline
6464
internal let _compressionEnabledOnServer: Bool
@@ -70,8 +70,8 @@ internal final class AsyncResponseStreamWriterDelegate<Response>: AsyncWriterDel
7070
internal init(
7171
context: GRPCAsyncServerCallContext,
7272
compressionIsEnabled: Bool,
73-
send: @escaping (Response, MessageMetadata) -> Void,
74-
finish: @escaping (GRPCStatus) -> Void
73+
send: @escaping @Sendable(Response, MessageMetadata) -> Void,
74+
finish: @escaping @Sendable(GRPCStatus) -> Void
7575
) {
7676
self._context = context
7777
self._compressionEnabledOnServer = compressionIsEnabled

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerCallContext.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ import NIOHPACK
2828
// make for a surprising API.
2929
//
3030
// We also considered an `actor` but that felt clunky at the point of use since adopters would need
31-
// to `await` the retrieval of a logger or the updating of the trailers and each would requrie a
31+
// to `await` the retrieval of a logger or the updating of the trailers and each would require a
3232
// promise to glue the NIO and async-await paradigms in the handler.
33+
//
34+
// Note: this is `@unchecked Sendable`; all mutable state is protected by a lock.
3335
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
34-
public final class GRPCAsyncServerCallContext {
36+
public final class GRPCAsyncServerCallContext: @unchecked Sendable {
3537
private let lock = Lock()
3638

3739
/// Metadata for this request.

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerHandler.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import NIOHPACK
2121
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
2222
public struct GRPCAsyncServerHandler<
2323
Serializer: MessageSerializer,
24-
Deserializer: MessageDeserializer
25-
>: GRPCServerHandlerProtocol {
24+
Deserializer: MessageDeserializer,
25+
Request: Sendable,
26+
Response: Sendable
27+
>: GRPCServerHandlerProtocol where Serializer.Input == Response, Deserializer.Output == Request {
2628
@usableFromInline
27-
internal let _handler: AsyncServerHandler<Serializer, Deserializer>
29+
internal let _handler: AsyncServerHandler<Serializer, Deserializer, Request, Response>
2830

2931
public func receiveMetadata(_ metadata: HPACKHeaders) {
3032
self._handler.receiveMetadata(metadata)
@@ -153,13 +155,10 @@ extension GRPCAsyncServerHandler {
153155
@usableFromInline
154156
internal final class AsyncServerHandler<
155157
Serializer: MessageSerializer,
156-
Deserializer: MessageDeserializer
157-
>: GRPCServerHandlerProtocol {
158-
@usableFromInline
159-
internal typealias Request = Deserializer.Output
160-
@usableFromInline
161-
internal typealias Response = Serializer.Input
162-
158+
Deserializer: MessageDeserializer,
159+
Request: Sendable,
160+
Response: Sendable
161+
>: GRPCServerHandlerProtocol where Serializer.Input == Response, Deserializer.Output == Request {
163162
/// A response serializer.
164163
@usableFromInline
165164
internal let serializer: Serializer
@@ -182,7 +181,7 @@ internal final class AsyncServerHandler<
182181

183182
/// The user provided function to execute.
184183
@usableFromInline
185-
internal let userHandler: (
184+
internal let userHandler: @Sendable(
186185
GRPCAsyncRequestStream<Request>,
187186
GRPCAsyncResponseStreamWriter<Response>,
188187
GRPCAsyncServerCallContext
@@ -531,6 +530,7 @@ internal final class AsyncServerHandler<
531530
}
532531
}
533532

533+
@Sendable
534534
@inlinable
535535
internal func interceptResponse(_ response: Response, metadata: MessageMetadata) {
536536
if self.context.eventLoop.inEventLoop {
@@ -587,6 +587,7 @@ internal final class AsyncServerHandler<
587587
}
588588
}
589589

590+
@Sendable
590591
@inlinable
591592
internal func responseStreamDrained(_ status: GRPCStatus) {
592593
if self.context.eventLoop.inEventLoop {

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerStreamingCall.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import NIOHPACK
1919

2020
/// Async-await variant of `ServerStreamingCall`.
2121
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
22-
public struct GRPCAsyncServerStreamingCall<Request, Response> {
22+
public struct GRPCAsyncServerStreamingCall<Request: Sendable, Response: Sendable> {
2323
private let call: Call<Request, Response>
2424
private let responseParts: StreamingResponseParts<Response>
2525
private let responseSource: PassthroughMessageSource<Response, Error>

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncUnaryCall.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import NIOHPACK
2222
/// Note: while this object is a `struct`, its implementation delegates to `Call`. It therefore
2323
/// has reference semantics.
2424
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
25-
public struct GRPCAsyncUnaryCall<Request, Response> {
25+
public struct GRPCAsyncUnaryCall<Request: Sendable, Response: Sendable> {
2626
private let call: Call<Request, Response>
2727
private let responseParts: UnaryResponseParts<Response>
2828

0 commit comments

Comments
 (0)