Skip to content

Commit a48b5dd

Browse files
authored
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
1 parent c7d6521 commit a48b5dd

File tree

12 files changed

+85
-91
lines changed

12 files changed

+85
-91
lines changed

Sources/Examples/Echo/Model/echo.grpc.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -613,31 +613,31 @@ extension Echo_EchoProvider {
613613

614614
/// To implement a server, implement an object which conforms to this protocol.
615615
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
616-
public protocol Echo_EchoAsyncProvider: CallHandlerProvider {
616+
public protocol Echo_EchoAsyncProvider: CallHandlerProvider, Sendable {
617617
static var serviceDescriptor: GRPCServiceDescriptor { get }
618618
var interceptors: Echo_EchoServerInterceptorFactoryProtocol? { get }
619619

620620
/// Immediately returns an echo of a request.
621-
@Sendable func get(
621+
func get(
622622
request: Echo_EchoRequest,
623623
context: GRPCAsyncServerCallContext
624624
) async throws -> Echo_EchoResponse
625625

626626
/// Splits a request into words and returns each word in a stream of messages.
627-
@Sendable func expand(
627+
func expand(
628628
request: Echo_EchoRequest,
629629
responseStream: GRPCAsyncResponseStreamWriter<Echo_EchoResponse>,
630630
context: GRPCAsyncServerCallContext
631631
) async throws
632632

633633
/// Collects a stream of messages and returns them concatenated when the caller closes.
634-
@Sendable func collect(
634+
func collect(
635635
requestStream: GRPCAsyncRequestStream<Echo_EchoRequest>,
636636
context: GRPCAsyncServerCallContext
637637
) async throws -> Echo_EchoResponse
638638

639639
/// Streams back messages as they are received in an input stream.
640-
@Sendable func update(
640+
func update(
641641
requestStream: GRPCAsyncRequestStream<Echo_EchoRequest>,
642642
responseStream: GRPCAsyncResponseStreamWriter<Echo_EchoResponse>,
643643
context: GRPCAsyncServerCallContext
@@ -669,7 +669,7 @@ extension Echo_EchoAsyncProvider {
669669
requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
670670
responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
671671
interceptors: self.interceptors?.makeGetInterceptors() ?? [],
672-
wrapping: self.get(request:context:)
672+
wrapping: { try await self.get(request: $0, context: $1) }
673673
)
674674

675675
case "Expand":
@@ -678,7 +678,7 @@ extension Echo_EchoAsyncProvider {
678678
requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
679679
responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
680680
interceptors: self.interceptors?.makeExpandInterceptors() ?? [],
681-
wrapping: self.expand(request:responseStream:context:)
681+
wrapping: { try await self.expand(request: $0, responseStream: $1, context: $2) }
682682
)
683683

684684
case "Collect":
@@ -687,7 +687,7 @@ extension Echo_EchoAsyncProvider {
687687
requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
688688
responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
689689
interceptors: self.interceptors?.makeCollectInterceptors() ?? [],
690-
wrapping: self.collect(requestStream:context:)
690+
wrapping: { try await self.collect(requestStream: $0, context: $1) }
691691
)
692692

693693
case "Update":
@@ -696,7 +696,7 @@ extension Echo_EchoAsyncProvider {
696696
requestDeserializer: ProtobufDeserializer<Echo_EchoRequest>(),
697697
responseSerializer: ProtobufSerializer<Echo_EchoResponse>(),
698698
interceptors: self.interceptors?.makeUpdateInterceptors() ?? [],
699-
wrapping: self.update(requestStream:responseStream:context:)
699+
wrapping: { try await self.update(requestStream: $0, responseStream: $1, context: $2) }
700700
)
701701

702702
default:
@@ -705,7 +705,7 @@ extension Echo_EchoAsyncProvider {
705705
}
706706
}
707707

708-
public protocol Echo_EchoServerInterceptorFactoryProtocol {
708+
public protocol Echo_EchoServerInterceptorFactoryProtocol: Sendable {
709709

710710
/// - Returns: Interceptors to use when handling 'get'.
711711
/// Defaults to calling `self.makeInterceptors()`.

Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,12 @@ extension Helloworld_GreeterProvider {
252252
///
253253
/// To implement a server, implement an object which conforms to this protocol.
254254
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
255-
public protocol Helloworld_GreeterAsyncProvider: CallHandlerProvider {
255+
public protocol Helloworld_GreeterAsyncProvider: CallHandlerProvider, Sendable {
256256
static var serviceDescriptor: GRPCServiceDescriptor { get }
257257
var interceptors: Helloworld_GreeterServerInterceptorFactoryProtocol? { get }
258258

259259
/// Sends a greeting.
260-
@Sendable func sayHello(
260+
func sayHello(
261261
request: Helloworld_HelloRequest,
262262
context: GRPCAsyncServerCallContext
263263
) async throws -> Helloworld_HelloReply
@@ -288,7 +288,7 @@ extension Helloworld_GreeterAsyncProvider {
288288
requestDeserializer: ProtobufDeserializer<Helloworld_HelloRequest>(),
289289
responseSerializer: ProtobufSerializer<Helloworld_HelloReply>(),
290290
interceptors: self.interceptors?.makeSayHelloInterceptors() ?? [],
291-
wrapping: self.sayHello(request:context:)
291+
wrapping: { try await self.sayHello(request: $0, context: $1) }
292292
)
293293

294294
default:
@@ -297,7 +297,7 @@ extension Helloworld_GreeterAsyncProvider {
297297
}
298298
}
299299

300-
public protocol Helloworld_GreeterServerInterceptorFactoryProtocol {
300+
public protocol Helloworld_GreeterServerInterceptorFactoryProtocol: Sendable {
301301

302302
/// - Returns: Interceptors to use when handling 'sayHello'.
303303
/// Defaults to calling `self.makeInterceptors()`.

Sources/Examples/RouteGuide/Model/route_guide.grpc.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ extension Routeguide_RouteGuideProvider {
530530
///
531531
/// To implement a server, implement an object which conforms to this protocol.
532532
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
533-
public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
533+
public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider, Sendable {
534534
static var serviceDescriptor: GRPCServiceDescriptor { get }
535535
var interceptors: Routeguide_RouteGuideServerInterceptorFactoryProtocol? { get }
536536

@@ -540,7 +540,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
540540
///
541541
/// A feature with an empty name is returned if there's no feature at the given
542542
/// position.
543-
@Sendable func getFeature(
543+
func getFeature(
544544
request: Routeguide_Point,
545545
context: GRPCAsyncServerCallContext
546546
) async throws -> Routeguide_Feature
@@ -551,7 +551,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
551551
/// streamed rather than returned at once (e.g. in a response message with a
552552
/// repeated field), as the rectangle may cover a large area and contain a
553553
/// huge number of features.
554-
@Sendable func listFeatures(
554+
func listFeatures(
555555
request: Routeguide_Rectangle,
556556
responseStream: GRPCAsyncResponseStreamWriter<Routeguide_Feature>,
557557
context: GRPCAsyncServerCallContext
@@ -561,7 +561,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
561561
///
562562
/// Accepts a stream of Points on a route being traversed, returning a
563563
/// RouteSummary when traversal is completed.
564-
@Sendable func recordRoute(
564+
func recordRoute(
565565
requestStream: GRPCAsyncRequestStream<Routeguide_Point>,
566566
context: GRPCAsyncServerCallContext
567567
) async throws -> Routeguide_RouteSummary
@@ -570,7 +570,7 @@ public protocol Routeguide_RouteGuideAsyncProvider: CallHandlerProvider {
570570
///
571571
/// Accepts a stream of RouteNotes sent while a route is being traversed,
572572
/// while receiving other RouteNotes (e.g. from other users).
573-
@Sendable func routeChat(
573+
func routeChat(
574574
requestStream: GRPCAsyncRequestStream<Routeguide_RouteNote>,
575575
responseStream: GRPCAsyncResponseStreamWriter<Routeguide_RouteNote>,
576576
context: GRPCAsyncServerCallContext
@@ -602,7 +602,7 @@ extension Routeguide_RouteGuideAsyncProvider {
602602
requestDeserializer: ProtobufDeserializer<Routeguide_Point>(),
603603
responseSerializer: ProtobufSerializer<Routeguide_Feature>(),
604604
interceptors: self.interceptors?.makeGetFeatureInterceptors() ?? [],
605-
wrapping: self.getFeature(request:context:)
605+
wrapping: { try await self.getFeature(request: $0, context: $1) }
606606
)
607607

608608
case "ListFeatures":
@@ -611,7 +611,7 @@ extension Routeguide_RouteGuideAsyncProvider {
611611
requestDeserializer: ProtobufDeserializer<Routeguide_Rectangle>(),
612612
responseSerializer: ProtobufSerializer<Routeguide_Feature>(),
613613
interceptors: self.interceptors?.makeListFeaturesInterceptors() ?? [],
614-
wrapping: self.listFeatures(request:responseStream:context:)
614+
wrapping: { try await self.listFeatures(request: $0, responseStream: $1, context: $2) }
615615
)
616616

617617
case "RecordRoute":
@@ -620,7 +620,7 @@ extension Routeguide_RouteGuideAsyncProvider {
620620
requestDeserializer: ProtobufDeserializer<Routeguide_Point>(),
621621
responseSerializer: ProtobufSerializer<Routeguide_RouteSummary>(),
622622
interceptors: self.interceptors?.makeRecordRouteInterceptors() ?? [],
623-
wrapping: self.recordRoute(requestStream:context:)
623+
wrapping: { try await self.recordRoute(requestStream: $0, context: $1) }
624624
)
625625

626626
case "RouteChat":
@@ -629,7 +629,7 @@ extension Routeguide_RouteGuideAsyncProvider {
629629
requestDeserializer: ProtobufDeserializer<Routeguide_RouteNote>(),
630630
responseSerializer: ProtobufSerializer<Routeguide_RouteNote>(),
631631
interceptors: self.interceptors?.makeRouteChatInterceptors() ?? [],
632-
wrapping: self.routeChat(requestStream:responseStream:context:)
632+
wrapping: { try await self.routeChat(requestStream: $0, responseStream: $1, context: $2) }
633633
)
634634

635635
default:
@@ -638,7 +638,7 @@ extension Routeguide_RouteGuideAsyncProvider {
638638
}
639639
}
640640

641-
public protocol Routeguide_RouteGuideServerInterceptorFactoryProtocol {
641+
public protocol Routeguide_RouteGuideServerInterceptorFactoryProtocol: Sendable {
642642

643643
/// - Returns: Interceptors to use when handling 'getFeature'.
644644
/// Defaults to calling `self.makeInterceptors()`.

Sources/GRPC/Interceptor/ServerInterceptors.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import NIOCore
4242
/// require any extra attention. However, if work is done on a `DispatchQueue` or _other_
4343
/// `EventLoop` then implementers should ensure that they use `context` from the correct
4444
/// `EventLoop`.
45-
open class ServerInterceptor<Request, Response> {
45+
open class ServerInterceptor<Request, Response>: @unchecked Sendable {
4646
public init() {}
4747

4848
/// Called when the interceptor has received a request part to handle.

0 commit comments

Comments
 (0)