Browse Source

Fix a handful of Sendable warnings (#1419)

Motivation:

We require all request/response types to be `Sendable` as they always cross a
thread boundary. Our various async sequences and async writers are conditionally
Sendable if their `Element` is `Sendable`, however, in practice they always will
be. We require them to be `Sendable` as they are arguments to `async` functions
(see SE-0338). For the same reason, the async server context (and by extension
the async server handler) must also be `Sendable`.

Modifications:

- Require passthrough message/source sequence to have `Sendable` elements so
  that they are unconditionally `Sendable`
- Make the async server context and handler `Sendable`
- Make the async writer error Sendable
- Remove a global variable from protoc-gen-grpc-swift
- Add appropriate conditions to extensions on `Call` which define functions
  requiring `Call` to be `Sendable`

Result:

Better `Sendable` support.
George Barnett 3 years ago
parent
commit
3426bcade3

+ 1 - 1
Sources/GRPC/AsyncAwaitSupport/AsyncWriter.swift

@@ -300,7 +300,7 @@ public struct GRPCAsyncWriterError: Error, Hashable {
   private let wrapped: Wrapped
 
   @usableFromInline
-  internal enum Wrapped {
+  internal enum Wrapped: Sendable {
     case tooManyPendingWrites
     case alreadyFinished
   }

+ 1 - 1
Sources/GRPC/AsyncAwaitSupport/Call+AsyncRequestStreamWriter.swift

@@ -16,7 +16,7 @@
 #if compiler(>=5.6)
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-extension Call {
+extension Call where Request: Sendable, Response: Sendable {
   internal func makeRequestStreamWriter() -> GRPCAsyncRequestStreamWriter<Request> {
     let delegate = GRPCAsyncRequestStreamWriter<Request>.Delegate(
       compressionEnabled: self.options.messageEncoding.enabledForRequests

+ 3 - 3
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncResponseStream.swift

@@ -18,7 +18,7 @@
 /// This is currently a wrapper around AsyncThrowingStream because we want to be
 /// able to swap out the implementation for something else in the future.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public struct GRPCAsyncResponseStream<Element>: AsyncSequence {
+public struct GRPCAsyncResponseStream<Element: Sendable>: AsyncSequence {
   @usableFromInline
   internal typealias WrappedStream = PassthroughMessageSequence<Element, Error>
 
@@ -51,8 +51,8 @@ public struct GRPCAsyncResponseStream<Element>: AsyncSequence {
 }
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-extension GRPCAsyncResponseStream: Sendable where Element: Sendable {}
+extension GRPCAsyncResponseStream: Sendable {}
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-extension GRPCAsyncResponseStream.Iterator: Sendable where Element: Sendable {}
+extension GRPCAsyncResponseStream.Iterator: Sendable {}
 
 #endif

+ 2 - 2
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerCallContext.swift

@@ -20,7 +20,7 @@ import NIOConcurrencyHelpers
 @preconcurrency import NIOHPACK
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public struct GRPCAsyncServerCallContext {
+public struct GRPCAsyncServerCallContext: Sendable {
   @usableFromInline
   let contextProvider: AsyncServerCallContextProvider
 
@@ -83,7 +83,7 @@ extension GRPCAsyncServerCallContext {
     }
   }
 
-  public struct Response {
+  public struct Response: Sendable {
     private let contextProvider: AsyncServerCallContextProvider
 
     /// Set the metadata to return at the start of the RPC.

+ 7 - 2
Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerHandler.swift

@@ -728,6 +728,11 @@ internal final class AsyncServerHandler<
   }
 }
 
+// Sendability is unchecked as all mutable state is accessed/modified from an appropriate event
+// loop.
+@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
+extension AsyncServerHandler: @unchecked Sendable {}
+
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 extension AsyncServerHandler: AsyncServerCallContextProvider {
   @usableFromInline
@@ -782,7 +787,7 @@ extension AsyncServerHandler: AsyncServerCallContextProvider {
 /// correct event loop.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 @usableFromInline
-protocol AsyncServerCallContextProvider {
+protocol AsyncServerCallContextProvider: Sendable {
   func setResponseHeaders(_ headers: HPACKHeaders) async throws
   func setResponseTrailers(_ trailers: HPACKHeaders) async throws
   func setResponseCompression(_ enabled: Bool) async throws
@@ -798,7 +803,7 @@ protocol AsyncServerCallContextProvider {
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 @usableFromInline
-internal struct ServerHandlerComponents<Request, Delegate: AsyncWriterDelegate> {
+internal struct ServerHandlerComponents<Request: Sendable, Delegate: AsyncWriterDelegate> {
   @usableFromInline
   internal let task: Task<Void, Never>
   @usableFromInline

+ 2 - 2
Sources/GRPC/AsyncAwaitSupport/GRPCClient+AsyncAwaitSupport.swift

@@ -322,7 +322,7 @@ extension GRPCClient {
   public func performAsyncBidirectionalStreamingCall<
     Request: SwiftProtobuf.Message & Sendable,
     Response: SwiftProtobuf.Message & Sendable,
-    RequestStream: AsyncSequence
+    RequestStream: AsyncSequence & Sendable
   >(
     path: String,
     requests: RequestStream,
@@ -343,7 +343,7 @@ extension GRPCClient {
   public func performAsyncBidirectionalStreamingCall<
     Request: GRPCPayload & Sendable,
     Response: GRPCPayload & Sendable,
-    RequestStream: AsyncSequence
+    RequestStream: AsyncSequence & Sendable
   >(
     path: String,
     requests: RequestStream,

+ 3 - 3
Sources/GRPC/AsyncAwaitSupport/PassthroughMessageSequence.swift

@@ -18,7 +18,7 @@
 /// An ``AsyncSequence`` adapter for a ``PassthroughMessageSource``.`
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 @usableFromInline
-internal struct PassthroughMessageSequence<Element, Failure: Error>: AsyncSequence {
+internal struct PassthroughMessageSequence<Element: Sendable, Failure: Error>: AsyncSequence {
   @usableFromInline
   internal typealias Element = Element
 
@@ -57,8 +57,8 @@ internal struct PassthroughMessageSequence<Element, Failure: Error>: AsyncSequen
 }
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-extension PassthroughMessageSequence: Sendable where Element: Sendable {}
+extension PassthroughMessageSequence: Sendable {}
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-extension PassthroughMessageSequence.Iterator: Sendable where Element: Sendable {}
+extension PassthroughMessageSequence.Iterator: Sendable {}
 
 #endif // compiler(>=5.6)

+ 2 - 2
Sources/GRPC/AsyncAwaitSupport/PassthroughMessageSource.swift

@@ -29,7 +29,7 @@ import NIOCore
 /// indicate that the sequence should end with an error.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 @usableFromInline
-internal final class PassthroughMessageSource<Element, Failure: Error> {
+internal final class PassthroughMessageSource<Element: Sendable, Failure: Error> {
   @usableFromInline
   internal typealias _ContinuationResult = Result<Element?, Error>
 
@@ -171,6 +171,6 @@ internal final class PassthroughMessageSource<Element, Failure: Error> {
 
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 // @unchecked is ok: mutable state is accessed/modified via a lock.
-extension PassthroughMessageSource: @unchecked Sendable where Element: Sendable {}
+extension PassthroughMessageSource: @unchecked Sendable {}
 
 #endif // compiler(>=5.6)

+ 1 - 1
Sources/GRPC/ConnectivityState.swift

@@ -20,7 +20,7 @@ import NIOCore
 
 /// The connectivity state of a client connection. Note that this is heavily lifted from the gRPC
 /// documentation: https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md.
-public enum ConnectivityState {
+public enum ConnectivityState: GRPCSendable {
   /// This is the state where the channel has not yet been created.
   case idle
 

+ 7 - 4
Sources/protoc-gen-grpc-swift/main.swift

@@ -80,12 +80,11 @@ func outputFileName(
   }
 }
 
-var generatedFiles: [String: Int] = [:]
-
 func uniqueOutputFileName(
   component: String,
   fileDescriptor: FileDescriptor,
-  fileNamingOption: FileNaming
+  fileNamingOption: FileNaming,
+  generatedFiles: inout [String: Int]
 ) -> String {
   let defaultName = outputFileName(
     component: component,
@@ -121,6 +120,9 @@ func main() throws {
   // Build the SwiftProtobufPluginLibrary model of the plugin input
   let descriptorSet = DescriptorSet(protos: request.protoFile)
 
+  // A count of generated files by desired name (actual name may differ to avoid collisions).
+  var generatedFiles: [String: Int] = [:]
+
   // Only generate output for services.
   for name in request.fileToGenerate {
     let fileDescriptor = descriptorSet.lookupFileDescriptor(protoName: name)
@@ -128,7 +130,8 @@ func main() throws {
       let grpcFileName = uniqueOutputFileName(
         component: "grpc",
         fileDescriptor: fileDescriptor,
-        fileNamingOption: options.fileNaming
+        fileNamingOption: options.fileNaming,
+        generatedFiles: &generatedFiles
       )
       let grpcGenerator = Generator(fileDescriptor, options: options)
       var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()