|
15 | 15 | */
|
16 | 16 | #if compiler(>=5.5)
|
17 | 17 |
|
18 |
| -import _NIOConcurrency |
19 | 18 | import NIOHPACK
|
20 | 19 |
|
21 | 20 | /// Async-await variant of BidirectionalStreamingCall.
|
22 | 21 | @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
23 | 22 | public struct GRPCAsyncBidirectionalStreamingCall<Request, Response> {
|
24 | 23 | private let call: Call<Request, Response>
|
25 | 24 | private let responseParts: StreamingResponseParts<Response>
|
| 25 | + private let responseSource: PassthroughMessageSource<Response, Error> |
| 26 | + |
| 27 | + /// A request stream writer for sending messages to the server. |
| 28 | + public let requestStream: GRPCAsyncRequestStreamWriter<Request> |
26 | 29 |
|
27 | 30 | /// The stream of responses from the server.
|
28 | 31 | public let responses: GRPCAsyncResponseStream<Response>
|
@@ -74,93 +77,59 @@ public struct GRPCAsyncBidirectionalStreamingCall<Request, Response> {
|
74 | 77 |
|
75 | 78 | private init(call: Call<Request, Response>) {
|
76 | 79 | self.call = call
|
77 |
| - // Initialise `responseParts` with an empty response handler because we |
78 |
| - // provide the responses as an AsyncSequence in `responseStream`. |
79 | 80 | self.responseParts = StreamingResponseParts(on: call.eventLoop) { _ in }
|
80 |
| - |
81 |
| - // Call and StreamingResponseParts are reference types so we grab a |
82 |
| - // referecence to them here to avoid capturing mutable self in the closure |
83 |
| - // passed to the AsyncThrowingStream initializer. |
84 |
| - // |
85 |
| - // The alternative would be to declare the responseStream as: |
86 |
| - // ``` |
87 |
| - // public private(set) var responseStream: AsyncThrowingStream<ResponsePayload>! |
88 |
| - // ``` |
89 |
| - // |
90 |
| - // UPDATE: Additionally we expect to replace this soon with an AsyncSequence |
91 |
| - // implementation that supports yielding values from outside the closure. |
92 |
| - let call = self.call |
93 |
| - let responseParts = self.responseParts |
94 |
| - let responseStream = AsyncThrowingStream(Response.self) { continuation in |
95 |
| - call.invokeStreamingRequests { error in |
96 |
| - responseParts.handleError(error) |
97 |
| - continuation.finish(throwing: error) |
98 |
| - } onResponsePart: { responsePart in |
99 |
| - responseParts.handle(responsePart) |
100 |
| - switch responsePart { |
101 |
| - case let .message(response): |
102 |
| - continuation.yield(response) |
103 |
| - case .metadata: |
104 |
| - break |
105 |
| - case .end: |
106 |
| - continuation.finish() |
107 |
| - } |
108 |
| - } |
109 |
| - } |
110 |
| - self.responses = .init(responseStream) |
| 81 | + self.responseSource = PassthroughMessageSource<Response, Error>() |
| 82 | + self.responses = .init(PassthroughMessageSequence(consuming: self.responseSource)) |
| 83 | + self.requestStream = call.makeRequestStreamWriter() |
111 | 84 | }
|
112 | 85 |
|
113 | 86 | /// We expose this as the only non-private initializer so that the caller
|
114 | 87 | /// knows that invocation is part of initialisation.
|
115 | 88 | internal static func makeAndInvoke(call: Call<Request, Response>) -> Self {
|
116 |
| - Self(call: call) |
117 |
| - } |
118 |
| - |
119 |
| - // MARK: - Requests |
120 |
| - |
121 |
| - /// Sends a message to the service. |
122 |
| - /// |
123 |
| - /// - Important: Callers must terminate the stream of messages by calling `sendEnd()`. |
124 |
| - /// |
125 |
| - /// - Parameters: |
126 |
| - /// - message: The message to send. |
127 |
| - /// - compression: Whether compression should be used for this message. Ignored if compression |
128 |
| - /// was not enabled for the RPC. |
129 |
| - public func sendMessage( |
130 |
| - _ message: Request, |
131 |
| - compression: Compression = .deferToCallDefault |
132 |
| - ) async throws { |
133 |
| - let compress = self.call.compress(compression) |
134 |
| - let promise = self.call.eventLoop.makePromise(of: Void.self) |
135 |
| - self.call.send(.message(message, .init(compress: compress, flush: true)), promise: promise) |
136 |
| - // TODO: This waits for the message to be written to the socket. We should probably just wait for it to be written to the channel? |
137 |
| - try await promise.futureResult.get() |
138 |
| - } |
139 |
| - |
140 |
| - /// Sends a sequence of messages to the service. |
141 |
| - /// |
142 |
| - /// - Important: Callers must terminate the stream of messages by calling `sendEnd()`. |
143 |
| - /// |
144 |
| - /// - Parameters: |
145 |
| - /// - messages: The sequence of messages to send. |
146 |
| - /// - compression: Whether compression should be used for this message. Ignored if compression |
147 |
| - /// was not enabled for the RPC. |
148 |
| - public func sendMessages<S>( |
149 |
| - _ messages: S, |
150 |
| - compression: Compression = .deferToCallDefault |
151 |
| - ) async throws where S: Sequence, S.Element == Request { |
152 |
| - let promise = self.call.eventLoop.makePromise(of: Void.self) |
153 |
| - self.call.sendMessages(messages, compression: compression, promise: promise) |
154 |
| - try await promise.futureResult.get() |
| 89 | + let asyncCall = Self(call: call) |
| 90 | + |
| 91 | + asyncCall.call.invokeStreamingRequests( |
| 92 | + onError: { error in |
| 93 | + asyncCall.responseParts.handleError(error) |
| 94 | + asyncCall.responseSource.finish(throwing: error) |
| 95 | + }, |
| 96 | + onResponsePart: AsyncCall.makeResponsePartHandler( |
| 97 | + responseParts: asyncCall.responseParts, |
| 98 | + responseSource: asyncCall.responseSource |
| 99 | + ) |
| 100 | + ) |
| 101 | + |
| 102 | + return asyncCall |
155 | 103 | }
|
| 104 | +} |
156 | 105 |
|
157 |
| - /// Terminates a stream of messages sent to the service. |
158 |
| - /// |
159 |
| - /// - Important: This should only ever be called once. |
160 |
| - public func sendEnd() async throws { |
161 |
| - let promise = self.call.eventLoop.makePromise(of: Void.self) |
162 |
| - self.call.send(.end, promise: promise) |
163 |
| - try await promise.futureResult.get() |
| 106 | +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) |
| 107 | +internal enum AsyncCall { |
| 108 | + internal static func makeResponsePartHandler<Response>( |
| 109 | + responseParts: StreamingResponseParts<Response>, |
| 110 | + responseSource: PassthroughMessageSource<Response, Error> |
| 111 | + ) -> (GRPCClientResponsePart<Response>) -> Void { |
| 112 | + return { responsePart in |
| 113 | + // Handle the metadata, trailers and status. |
| 114 | + responseParts.handle(responsePart) |
| 115 | + |
| 116 | + // Handle the response messages and status. |
| 117 | + switch responsePart { |
| 118 | + case .metadata: |
| 119 | + () |
| 120 | + |
| 121 | + case let .message(response): |
| 122 | + // TODO: when we support backpressure we will need to stop ignoring the return value. |
| 123 | + _ = responseSource.yield(response) |
| 124 | + |
| 125 | + case let .end(status, _): |
| 126 | + if status.isOk { |
| 127 | + responseSource.finish() |
| 128 | + } else { |
| 129 | + responseSource.finish(throwing: status) |
| 130 | + } |
| 131 | + } |
| 132 | + } |
164 | 133 | }
|
165 | 134 | }
|
166 | 135 |
|
|
0 commit comments