Browse Source

Improve Sendable checking for server code (#1605)

Motivation:

The server handler protocol must be Sendable so that any methods on the
handler are also Sendable as annotating methods as `@Sendable` does not
work as expected. It follows from this that generated server interceptor
factories must also be Sendable (they already are for clients). This
puts the `ServerInterceptor` class in an awkward position: it must be
Sendable but is not inherently thread-safe (these restrictions are
documented and existed before Sendable checking was introduced to
Swift). The `ClientInterceptor` has the same restrictions and is
`@unchecked Sendable` so we elect to do the same for the
`ServerInterceptor`.

Modifications:

- Make generated server handlers and server interceptor factories Sendable
- Make `ServerInterceptor` `@unchecked Sendable`
- Regenerate
- Fix some warnings in test code

Result:

Better Sendable checking
George Barnett 2 years ago
parent
commit
a48b5dd5c0

+ 10 - 10
Sources/Examples/Echo/Model/echo.grpc.swift

@@ -613,31 +613,31 @@ extension Echo_EchoProvider {
 
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Echo_EchoAsyncProvider: CallHandlerProvider {
+public protocol Echo_EchoAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Echo_EchoServerInterceptorFactoryProtocol? { get }
 
   /// Immediately returns an echo of a request.
-  @Sendable func get(
+  func get(
     request: Echo_EchoRequest,
     context: GRPCAsyncServerCallContext
   ) async throws -> Echo_EchoResponse
 
   /// Splits a request into words and returns each word in a stream of messages.
-  @Sendable func expand(
+  func expand(
     request: Echo_EchoRequest,
     responseStream: GRPCAsyncResponseStreamWriter<Echo_EchoResponse>,
     context: GRPCAsyncServerCallContext
   ) async throws
 
   /// Collects a stream of messages and returns them concatenated when the caller closes.
-  @Sendable func collect(
+  func collect(
     requestStream: GRPCAsyncRequestStream<Echo_EchoRequest>,
     context: GRPCAsyncServerCallContext
   ) async throws -> Echo_EchoResponse
 
   /// Streams back messages as they are received in an input stream.
-  @Sendable func update(
+  func update(
     requestStream: GRPCAsyncRequestStream<Echo_EchoRequest>,
     responseStream: GRPCAsyncResponseStreamWriter<Echo_EchoResponse>,
     context: GRPCAsyncServerCallContext
@@ -669,7 +669,7 @@ extension Echo_EchoAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
         responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
         interceptors: self.interceptors?.makeGetInterceptors() ?? [],
-        wrapping: self.get(request:context:)
+        wrapping: { try await self.get(request: $0, context: $1) }
       )
 
     case "Expand":
@@ -678,7 +678,7 @@ extension Echo_EchoAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
         responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
         interceptors: self.interceptors?.makeExpandInterceptors() ?? [],
-        wrapping: self.expand(request:responseStream:context:)
+        wrapping: { try await self.expand(request: $0, responseStream: $1, context: $2) }
       )
 
     case "Collect":
@@ -687,7 +687,7 @@ extension Echo_EchoAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
         responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
         interceptors: self.interceptors?.makeCollectInterceptors() ?? [],
-        wrapping: self.collect(requestStream:context:)
+        wrapping: { try await self.collect(requestStream: $0, context: $1) }
       )
 
     case "Update":
@@ -696,7 +696,7 @@ extension Echo_EchoAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
         responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
         interceptors: self.interceptors?.makeUpdateInterceptors() ?? [],
-        wrapping: self.update(requestStream:responseStream:context:)
+        wrapping: { try await self.update(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     default:
@@ -705,7 +705,7 @@ extension Echo_EchoAsyncProvider {
   }
 }
 
-public protocol Echo_EchoServerInterceptorFactoryProtocol {
+public protocol Echo_EchoServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'get'.
   ///   Defaults to calling `self.makeInterceptors()`.

+ 4 - 4
Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

@@ -252,12 +252,12 @@ extension Helloworld_GreeterProvider {
 ///
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Helloworld_GreeterAsyncProvider: CallHandlerProvider {
+public protocol Helloworld_GreeterAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Helloworld_GreeterServerInterceptorFactoryProtocol? { get }
 
   /// Sends a greeting.
-  @Sendable func sayHello(
+  func sayHello(
     request: Helloworld_HelloRequest,
     context: GRPCAsyncServerCallContext
   ) async throws -> Helloworld_HelloReply
@@ -288,7 +288,7 @@ extension Helloworld_GreeterAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Helloworld_HelloRequest>(),
         responseSerializer: ProtobufSerializer<Helloworld_HelloReply>(),
         interceptors: self.interceptors?.makeSayHelloInterceptors() ?? [],
-        wrapping: self.sayHello(request:context:)
+        wrapping: { try await self.sayHello(request: $0, context: $1) }
       )
 
     default:
@@ -297,7 +297,7 @@ extension Helloworld_GreeterAsyncProvider {
   }
 }
 
-public protocol Helloworld_GreeterServerInterceptorFactoryProtocol {
+public protocol Helloworld_GreeterServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'sayHello'.
   ///   Defaults to calling `self.makeInterceptors()`.

+ 10 - 10
Sources/Examples/RouteGuide/Model/route_guide.grpc.swift

@@ -530,7 +530,7 @@ extension Routeguide_RouteGuideProvider {
 ///
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
+public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Routeguide_RouteGuideServerInterceptorFactoryProtocol? { get }
 
@@ -540,7 +540,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
   ///
   /// A feature with an empty name is returned if there's no feature at the given
   /// position.
-  @Sendable func getFeature(
+  func getFeature(
     request: Routeguide_Point,
     context: GRPCAsyncServerCallContext
   ) async throws -> Routeguide_Feature
@@ -551,7 +551,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
   /// streamed rather than returned at once (e.g. in a response message with a
   /// repeated field), as the rectangle may cover a large area and contain a
   /// huge number of features.
-  @Sendable func listFeatures(
+  func listFeatures(
     request: Routeguide_Rectangle,
     responseStream: GRPCAsyncResponseStreamWriter<Routeguide_Feature>,
     context: GRPCAsyncServerCallContext
@@ -561,7 +561,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
   ///
   /// Accepts a stream of Points on a route being traversed, returning a
   /// RouteSummary when traversal is completed.
-  @Sendable func recordRoute(
+  func recordRoute(
     requestStream: GRPCAsyncRequestStream<Routeguide_Point>,
     context: GRPCAsyncServerCallContext
   ) async throws -> Routeguide_RouteSummary
@@ -570,7 +570,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
   ///
   /// Accepts a stream of RouteNotes sent while a route is being traversed,
   /// while receiving other RouteNotes (e.g. from other users).
-  @Sendable func routeChat(
+  func routeChat(
     requestStream: GRPCAsyncRequestStream<Routeguide_RouteNote>,
     responseStream: GRPCAsyncResponseStreamWriter<Routeguide_RouteNote>,
     context: GRPCAsyncServerCallContext
@@ -602,7 +602,7 @@ extension Routeguide_RouteGuideAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Routeguide_Point>(),
         responseSerializer: ProtobufSerializer<Routeguide_Feature>(),
         interceptors: self.interceptors?.makeGetFeatureInterceptors() ?? [],
-        wrapping: self.getFeature(request:context:)
+        wrapping: { try await self.getFeature(request: $0, context: $1) }
       )
 
     case "ListFeatures":
@@ -611,7 +611,7 @@ extension Routeguide_RouteGuideAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Routeguide_Rectangle>(),
         responseSerializer: ProtobufSerializer<Routeguide_Feature>(),
         interceptors: self.interceptors?.makeListFeaturesInterceptors() ?? [],
-        wrapping: self.listFeatures(request:responseStream:context:)
+        wrapping: { try await self.listFeatures(request: $0, responseStream: $1, context: $2) }
       )
 
     case "RecordRoute":
@@ -620,7 +620,7 @@ extension Routeguide_RouteGuideAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Routeguide_Point>(),
         responseSerializer: ProtobufSerializer<Routeguide_RouteSummary>(),
         interceptors: self.interceptors?.makeRecordRouteInterceptors() ?? [],
-        wrapping: self.recordRoute(requestStream:context:)
+        wrapping: { try await self.recordRoute(requestStream: $0, context: $1) }
       )
 
     case "RouteChat":
@@ -629,7 +629,7 @@ extension Routeguide_RouteGuideAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Routeguide_RouteNote>(),
         responseSerializer: ProtobufSerializer<Routeguide_RouteNote>(),
         interceptors: self.interceptors?.makeRouteChatInterceptors() ?? [],
-        wrapping: self.routeChat(requestStream:responseStream:context:)
+        wrapping: { try await self.routeChat(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     default:
@@ -638,7 +638,7 @@ extension Routeguide_RouteGuideAsyncProvider {
   }
 }
 
-public protocol Routeguide_RouteGuideServerInterceptorFactoryProtocol {
+public protocol Routeguide_RouteGuideServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'getFeature'.
   ///   Defaults to calling `self.makeInterceptors()`.

+ 1 - 1
Sources/GRPC/Interceptor/ServerInterceptors.swift

@@ -42,7 +42,7 @@ import NIOCore
 /// require any extra attention. However, if work is done on a `DispatchQueue` or _other_
 /// `EventLoop` then implementers should ensure that they use `context` from the correct
 /// `EventLoop`.
-open class ServerInterceptor<Request, Response> {
+open class ServerInterceptor<Request, Response>: @unchecked Sendable {
   public init() {}
 
   /// Called when the interceptor has received a request part to handle.

+ 26 - 26
Sources/GRPCInteroperabilityTestModels/Generated/test.grpc.swift

@@ -1251,18 +1251,18 @@ extension Grpc_Testing_TestServiceProvider {
 ///
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider {
+public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Grpc_Testing_TestServiceServerInterceptorFactoryProtocol? { get }
 
   /// One empty request followed by one empty response.
-  @Sendable func emptyCall(
+  func emptyCall(
     request: Grpc_Testing_Empty,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_Empty
 
   /// One request followed by one response.
-  @Sendable func unaryCall(
+  func unaryCall(
     request: Grpc_Testing_SimpleRequest,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_SimpleResponse
@@ -1270,14 +1270,14 @@ public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider {
   /// One request followed by one response. Response has cache control
   /// headers set such that a caching HTTP proxy (such as GFE) can
   /// satisfy subsequent requests.
-  @Sendable func cacheableUnaryCall(
+  func cacheableUnaryCall(
     request: Grpc_Testing_SimpleRequest,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_SimpleResponse
 
   /// One request followed by a sequence of responses (streamed download).
   /// The server returns the payload with client desired type and sizes.
-  @Sendable func streamingOutputCall(
+  func streamingOutputCall(
     request: Grpc_Testing_StreamingOutputCallRequest,
     responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
     context: GRPCAsyncServerCallContext
@@ -1285,7 +1285,7 @@ public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider {
 
   /// A sequence of requests followed by one response (streamed upload).
   /// The server returns the aggregated size of client payload as the result.
-  @Sendable func streamingInputCall(
+  func streamingInputCall(
     requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingInputCallRequest>,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_StreamingInputCallResponse
@@ -1293,7 +1293,7 @@ public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider {
   /// A sequence of requests with each request served by the server immediately.
   /// As one request could lead to multiple responses, this interface
   /// demonstrates the idea of full duplexing.
-  @Sendable func fullDuplexCall(
+  func fullDuplexCall(
     requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingOutputCallRequest>,
     responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
     context: GRPCAsyncServerCallContext
@@ -1303,7 +1303,7 @@ public protocol Grpc_Testing_TestServiceAsyncProvider: CallHandlerProvider {
   /// The server buffers all the client requests and then serves them in order. A
   /// stream of responses are returned to the client when the server starts with
   /// first request.
-  @Sendable func halfDuplexCall(
+  func halfDuplexCall(
     requestStream: GRPCAsyncRequestStream<Grpc_Testing_StreamingOutputCallRequest>,
     responseStream: GRPCAsyncResponseStreamWriter<Grpc_Testing_StreamingOutputCallResponse>,
     context: GRPCAsyncServerCallContext
@@ -1335,7 +1335,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_Empty>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_Empty>(),
         interceptors: self.interceptors?.makeEmptyCallInterceptors() ?? [],
-        wrapping: self.emptyCall(request:context:)
+        wrapping: { try await self.emptyCall(request: $0, context: $1) }
       )
 
     case "UnaryCall":
@@ -1344,7 +1344,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_SimpleRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_SimpleResponse>(),
         interceptors: self.interceptors?.makeUnaryCallInterceptors() ?? [],
-        wrapping: self.unaryCall(request:context:)
+        wrapping: { try await self.unaryCall(request: $0, context: $1) }
       )
 
     case "CacheableUnaryCall":
@@ -1353,7 +1353,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_SimpleRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_SimpleResponse>(),
         interceptors: self.interceptors?.makeCacheableUnaryCallInterceptors() ?? [],
-        wrapping: self.cacheableUnaryCall(request:context:)
+        wrapping: { try await self.cacheableUnaryCall(request: $0, context: $1) }
       )
 
     case "StreamingOutputCall":
@@ -1362,7 +1362,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_StreamingOutputCallRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_StreamingOutputCallResponse>(),
         interceptors: self.interceptors?.makeStreamingOutputCallInterceptors() ?? [],
-        wrapping: self.streamingOutputCall(request:responseStream:context:)
+        wrapping: { try await self.streamingOutputCall(request: $0, responseStream: $1, context: $2) }
       )
 
     case "StreamingInputCall":
@@ -1371,7 +1371,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_StreamingInputCallRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_StreamingInputCallResponse>(),
         interceptors: self.interceptors?.makeStreamingInputCallInterceptors() ?? [],
-        wrapping: self.streamingInputCall(requestStream:context:)
+        wrapping: { try await self.streamingInputCall(requestStream: $0, context: $1) }
       )
 
     case "FullDuplexCall":
@@ -1380,7 +1380,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_StreamingOutputCallRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_StreamingOutputCallResponse>(),
         interceptors: self.interceptors?.makeFullDuplexCallInterceptors() ?? [],
-        wrapping: self.fullDuplexCall(requestStream:responseStream:context:)
+        wrapping: { try await self.fullDuplexCall(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     case "HalfDuplexCall":
@@ -1389,7 +1389,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_StreamingOutputCallRequest>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_StreamingOutputCallResponse>(),
         interceptors: self.interceptors?.makeHalfDuplexCallInterceptors() ?? [],
-        wrapping: self.halfDuplexCall(requestStream:responseStream:context:)
+        wrapping: { try await self.halfDuplexCall(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     default:
@@ -1398,7 +1398,7 @@ extension Grpc_Testing_TestServiceAsyncProvider {
   }
 }
 
-public protocol Grpc_Testing_TestServiceServerInterceptorFactoryProtocol {
+public protocol Grpc_Testing_TestServiceServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'emptyCall'.
   ///   Defaults to calling `self.makeInterceptors()`.
@@ -1542,12 +1542,12 @@ extension Grpc_Testing_UnimplementedServiceProvider {
 ///
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Grpc_Testing_UnimplementedServiceAsyncProvider: CallHandlerProvider {
+public protocol Grpc_Testing_UnimplementedServiceAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Grpc_Testing_UnimplementedServiceServerInterceptorFactoryProtocol? { get }
 
   /// A call that no server should implement
-  @Sendable func unimplementedCall(
+  func unimplementedCall(
     request: Grpc_Testing_Empty,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_Empty
@@ -1578,7 +1578,7 @@ extension Grpc_Testing_UnimplementedServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_Empty>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_Empty>(),
         interceptors: self.interceptors?.makeUnimplementedCallInterceptors() ?? [],
-        wrapping: self.unimplementedCall(request:context:)
+        wrapping: { try await self.unimplementedCall(request: $0, context: $1) }
       )
 
     default:
@@ -1587,7 +1587,7 @@ extension Grpc_Testing_UnimplementedServiceAsyncProvider {
   }
 }
 
-public protocol Grpc_Testing_UnimplementedServiceServerInterceptorFactoryProtocol {
+public protocol Grpc_Testing_UnimplementedServiceServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'unimplementedCall'.
   ///   Defaults to calling `self.makeInterceptors()`.
@@ -1662,16 +1662,16 @@ extension Grpc_Testing_ReconnectServiceProvider {
 ///
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public protocol Grpc_Testing_ReconnectServiceAsyncProvider: CallHandlerProvider {
+public protocol Grpc_Testing_ReconnectServiceAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Grpc_Testing_ReconnectServiceServerInterceptorFactoryProtocol? { get }
 
-  @Sendable func start(
+  func start(
     request: Grpc_Testing_ReconnectParams,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_Empty
 
-  @Sendable func stop(
+  func stop(
     request: Grpc_Testing_Empty,
     context: GRPCAsyncServerCallContext
   ) async throws -> Grpc_Testing_ReconnectInfo
@@ -1702,7 +1702,7 @@ extension Grpc_Testing_ReconnectServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_ReconnectParams>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_Empty>(),
         interceptors: self.interceptors?.makeStartInterceptors() ?? [],
-        wrapping: self.start(request:context:)
+        wrapping: { try await self.start(request: $0, context: $1) }
       )
 
     case "Stop":
@@ -1711,7 +1711,7 @@ extension Grpc_Testing_ReconnectServiceAsyncProvider {
         requestDeserializer: ProtobufDeserializer<Grpc_Testing_Empty>(),
         responseSerializer: ProtobufSerializer<Grpc_Testing_ReconnectInfo>(),
         interceptors: self.interceptors?.makeStopInterceptors() ?? [],
-        wrapping: self.stop(request:context:)
+        wrapping: { try await self.stop(request: $0, context: $1) }
       )
 
     default:
@@ -1720,7 +1720,7 @@ extension Grpc_Testing_ReconnectServiceAsyncProvider {
   }
 }
 
-public protocol Grpc_Testing_ReconnectServiceServerInterceptorFactoryProtocol {
+public protocol Grpc_Testing_ReconnectServiceServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'start'.
   ///   Defaults to calling `self.makeInterceptors()`.

+ 2 - 2
Sources/GRPCInteroperabilityTestsImplementation/TestServiceAsyncProvider.swift

@@ -22,8 +22,8 @@ import NIOCore
 ///
 /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md#server
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-public class TestServiceAsyncProvider: Grpc_Testing_TestServiceAsyncProvider {
-  public var interceptors: Grpc_Testing_TestServiceServerInterceptorFactoryProtocol?
+public final class TestServiceAsyncProvider: Grpc_Testing_TestServiceAsyncProvider {
+  public let interceptors: Grpc_Testing_TestServiceServerInterceptorFactoryProtocol? = nil
 
   public init() {}
 

+ 7 - 8
Sources/protoc-gen-grpc-swift/Generator-Server+AsyncAwait.swift

@@ -29,7 +29,7 @@ extension Generator {
     self.println("/// To implement a server, implement an object which conforms to this protocol.")
     self.printAvailabilityForAsyncAwait()
     self.withIndentation(
-      "\(self.access) protocol \(self.asyncProviderName): CallHandlerProvider",
+      "\(self.access) protocol \(self.asyncProviderName): CallHandlerProvider, Sendable",
       braces: .curly
     ) {
       self.println("static var serviceDescriptor: GRPCServiceDescriptor { get }")
@@ -86,7 +86,7 @@ extension Generator {
       name: self.methodFunctionName,
       arguments: arguments,
       returnType: returnType,
-      sendable: true,
+      sendable: false,
       async: true,
       throws: true,
       bodyBuilder: nil
@@ -152,20 +152,19 @@ extension Generator {
               self.println("requestDeserializer: \(Types.deserializer(for: requestType))(),")
               self.println("responseSerializer: \(Types.serializer(for: responseType))(),")
               self.println("interceptors: self.interceptors?.\(interceptorFactory)() ?? [],")
+              let prefix = "wrapping: { try await self.\(functionName)"
               switch streamingType(self.method) {
               case .unary:
-                self.println("wrapping: self.\(functionName)(request:context:)")
+                self.println("\(prefix)(request: $0, context: $1) }")
 
               case .clientStreaming:
-                self.println("wrapping: self.\(functionName)(requestStream:context:)")
+                self.println("\(prefix)(requestStream: $0, context: $1) }")
 
               case .serverStreaming:
-                self.println("wrapping: self.\(functionName)(request:responseStream:context:)")
+                self.println("\(prefix)(request: $0, responseStream: $1, context: $2) }")
 
               case .bidirectionalStreaming:
-                self.println(
-                  "wrapping: self.\(functionName)(requestStream:responseStream:context:)"
-                )
+                self.println("\(prefix)(requestStream: $0, responseStream: $1, context: $2) }")
               }
             }
           }

+ 1 - 1
Sources/protoc-gen-grpc-swift/Generator-Server.swift

@@ -148,7 +148,7 @@ extension Generator {
   }
 
   private func printServerInterceptorFactoryProtocol() {
-    self.println("\(self.access) protocol \(self.serverInterceptorProtocolName) {")
+    self.println("\(self.access) protocol \(self.serverInterceptorProtocolName): Sendable {")
     self.withIndentation {
       // Method specific interceptors.
       for method in service.methods {

+ 18 - 18
Tests/GRPCTests/Codegen/Normalization/normalization.grpc.swift

@@ -803,49 +803,49 @@ extension Normalization_NormalizationProvider {
 
 /// To implement a server, implement an object which conforms to this protocol.
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
-internal protocol Normalization_NormalizationAsyncProvider: CallHandlerProvider {
+internal protocol Normalization_NormalizationAsyncProvider: CallHandlerProvider, Sendable {
   static var serviceDescriptor: GRPCServiceDescriptor { get }
   var interceptors: Normalization_NormalizationServerInterceptorFactoryProtocol? { get }
 
-  @Sendable func Unary(
+  func Unary(
     request: SwiftProtobuf.Google_Protobuf_Empty,
     context: GRPCAsyncServerCallContext
   ) async throws -> Normalization_FunctionName
 
-  @Sendable func unary(
+  func unary(
     request: SwiftProtobuf.Google_Protobuf_Empty,
     context: GRPCAsyncServerCallContext
   ) async throws -> Normalization_FunctionName
 
-  @Sendable func ServerStreaming(
+  func ServerStreaming(
     request: SwiftProtobuf.Google_Protobuf_Empty,
     responseStream: GRPCAsyncResponseStreamWriter<Normalization_FunctionName>,
     context: GRPCAsyncServerCallContext
   ) async throws
 
-  @Sendable func serverStreaming(
+  func serverStreaming(
     request: SwiftProtobuf.Google_Protobuf_Empty,
     responseStream: GRPCAsyncResponseStreamWriter<Normalization_FunctionName>,
     context: GRPCAsyncServerCallContext
   ) async throws
 
-  @Sendable func ClientStreaming(
+  func ClientStreaming(
     requestStream: GRPCAsyncRequestStream<SwiftProtobuf.Google_Protobuf_Empty>,
     context: GRPCAsyncServerCallContext
   ) async throws -> Normalization_FunctionName
 
-  @Sendable func clientStreaming(
+  func clientStreaming(
     requestStream: GRPCAsyncRequestStream<SwiftProtobuf.Google_Protobuf_Empty>,
     context: GRPCAsyncServerCallContext
   ) async throws -> Normalization_FunctionName
 
-  @Sendable func BidirectionalStreaming(
+  func BidirectionalStreaming(
     requestStream: GRPCAsyncRequestStream<SwiftProtobuf.Google_Protobuf_Empty>,
     responseStream: GRPCAsyncResponseStreamWriter<Normalization_FunctionName>,
     context: GRPCAsyncServerCallContext
   ) async throws
 
-  @Sendable func bidirectionalStreaming(
+  func bidirectionalStreaming(
     requestStream: GRPCAsyncRequestStream<SwiftProtobuf.Google_Protobuf_Empty>,
     responseStream: GRPCAsyncResponseStreamWriter<Normalization_FunctionName>,
     context: GRPCAsyncServerCallContext
@@ -877,7 +877,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeUnaryInterceptors() ?? [],
-        wrapping: self.Unary(request:context:)
+        wrapping: { try await self.Unary(request: $0, context: $1) }
       )
 
     case "unary":
@@ -886,7 +886,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeunaryInterceptors() ?? [],
-        wrapping: self.unary(request:context:)
+        wrapping: { try await self.unary(request: $0, context: $1) }
       )
 
     case "ServerStreaming":
@@ -895,7 +895,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeServerStreamingInterceptors() ?? [],
-        wrapping: self.ServerStreaming(request:responseStream:context:)
+        wrapping: { try await self.ServerStreaming(request: $0, responseStream: $1, context: $2) }
       )
 
     case "serverStreaming":
@@ -904,7 +904,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeserverStreamingInterceptors() ?? [],
-        wrapping: self.serverStreaming(request:responseStream:context:)
+        wrapping: { try await self.serverStreaming(request: $0, responseStream: $1, context: $2) }
       )
 
     case "ClientStreaming":
@@ -913,7 +913,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeClientStreamingInterceptors() ?? [],
-        wrapping: self.ClientStreaming(requestStream:context:)
+        wrapping: { try await self.ClientStreaming(requestStream: $0, context: $1) }
       )
 
     case "clientStreaming":
@@ -922,7 +922,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeclientStreamingInterceptors() ?? [],
-        wrapping: self.clientStreaming(requestStream:context:)
+        wrapping: { try await self.clientStreaming(requestStream: $0, context: $1) }
       )
 
     case "BidirectionalStreaming":
@@ -931,7 +931,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makeBidirectionalStreamingInterceptors() ?? [],
-        wrapping: self.BidirectionalStreaming(requestStream:responseStream:context:)
+        wrapping: { try await self.BidirectionalStreaming(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     case "bidirectionalStreaming":
@@ -940,7 +940,7 @@ extension Normalization_NormalizationAsyncProvider {
         requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
         responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
         interceptors: self.interceptors?.makebidirectionalStreamingInterceptors() ?? [],
-        wrapping: self.bidirectionalStreaming(requestStream:responseStream:context:)
+        wrapping: { try await self.bidirectionalStreaming(requestStream: $0, responseStream: $1, context: $2) }
       )
 
     default:
@@ -949,7 +949,7 @@ extension Normalization_NormalizationAsyncProvider {
   }
 }
 
-internal protocol Normalization_NormalizationServerInterceptorFactoryProtocol {
+internal protocol Normalization_NormalizationServerInterceptorFactoryProtocol: Sendable {
 
   /// - Returns: Interceptors to use when handling 'Unary'.
   ///   Defaults to calling `self.makeInterceptors()`.

+ 3 - 8
Tests/GRPCTests/EchoHelpers/Interceptors/EchoInterceptorFactories.swift

@@ -19,8 +19,7 @@ import GRPC
 // MARK: - Client
 
 internal final class EchoClientInterceptors: Echo_EchoClientInterceptorFactoryProtocol {
-  internal typealias Factory = @Sendable ()
-    -> ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>
+  typealias Factory = @Sendable () -> ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>
   private let factories: [Factory]
 
   internal init(_ factories: Factory...) {
@@ -51,17 +50,13 @@ internal final class EchoClientInterceptors: Echo_EchoClientInterceptorFactoryPr
 // MARK: - Server
 
 internal final class EchoServerInterceptors: Echo_EchoServerInterceptorFactoryProtocol {
-  internal typealias Factory = () -> ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>
-  private var factories: [Factory] = []
+  typealias Factory = @Sendable () -> ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>
+  private let factories: [Factory]
 
   internal init(_ factories: Factory...) {
     self.factories = factories
   }
 
-  internal func register(_ factory: @escaping Factory) {
-    self.factories.append(factory)
-  }
-
   private func makeInterceptors() -> [ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
     return self.factories.map { $0() }
   }

+ 1 - 1
Tests/GRPCTests/InterceptorsTests.swift

@@ -208,7 +208,7 @@ class NotReallyAuthServerInterceptor<Request: Message, Response: Message>:
   }
 }
 
-class HelloWorldServerInterceptorFactory: Helloworld_GreeterServerInterceptorFactoryProtocol {
+final class HelloWorldServerInterceptorFactory: Helloworld_GreeterServerInterceptorFactoryProtocol {
   func makeSayHelloInterceptors(
   ) -> [ServerInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>] {
     return [RemoteAddressExistsInterceptor(), NotReallyAuthServerInterceptor()]

+ 2 - 2
Tests/GRPCTests/ServerInterceptorTests.swift

@@ -178,7 +178,7 @@ class ServerInterceptorTests: GRPCTestCase {
   }
 }
 
-class EchoInterceptorFactory: Echo_EchoServerInterceptorFactoryProtocol {
+final class EchoInterceptorFactory: Echo_EchoServerInterceptorFactoryProtocol {
   private let interceptor: ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>
 
   init(interceptor: ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>) {
@@ -271,7 +271,7 @@ class EchoFromInterceptor: Echo_EchoProvider {
     return context.eventLoop.makeFailedFuture(GRPCStatus.processingError)
   }
 
-  class Interceptors: Echo_EchoServerInterceptorFactoryProtocol {
+  final class Interceptors: Echo_EchoServerInterceptorFactoryProtocol {
     func makeGetInterceptors() -> [ServerInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
       return [Interceptor()]
     }