blob: 8ae01157b7d013ed37dbe0c8777c693f026caf88 [file] [log] [blame]
Jorge Canizales9409ad82015-02-18 16:19:56 -08001/*
2 *
Yang Gao5fc90292015-02-20 09:46:22 -08003 * Copyright 2015, Google Inc.
Jorge Canizales9409ad82015-02-18 16:19:56 -08004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
Jorge Canizales5e0efd92015-02-17 18:23:58 -080034#import "GRPCCall.h"
35
Jorge Canizalesc2d7ecb2015-02-27 01:22:41 -080036#include <grpc/grpc.h>
37#include <grpc/support/grpc_time.h>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080038
39#import "GRPCMethodName.h"
40#import "private/GRPCChannel.h"
41#import "private/GRPCCompletionQueue.h"
42#import "private/GRPCDelegateWrapper.h"
43#import "private/GRPCMethodName+HTTP2Encoding.h"
murgatroid9969927d62015-04-24 13:32:48 -070044#import "private/GRPCWrappedCall.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080045#import "private/NSData+GRPC.h"
46#import "private/NSDictionary+GRPC.h"
47#import "private/NSError+GRPC.h"
48
49// A grpc_call_error represents a precondition failure when invoking the
50// grpc_call_* functions. If one ever happens, it's a bug in this library.
51//
52// TODO(jcanizales): Can an application shut down gracefully when a thread other
53// than the main one throws an exception?
54static void AssertNoErrorInCall(grpc_call_error error) {
55 if (error != GRPC_CALL_OK) {
56 @throw [NSException exceptionWithName:NSInternalInconsistencyException
57 reason:@"Precondition of grpc_call_* not met."
58 userInfo:nil];
59 }
60}
61
62@interface GRPCCall () <GRXWriteable>
63// Makes it readwrite.
64@property(atomic, strong) NSDictionary *responseMetadata;
65@end
66
67// The following methods of a C gRPC call object aren't reentrant, and thus
68// calls to them must be serialized:
69// - add_metadata
70// - invoke
71// - start_write
72// - writes_done
73// - start_read
74// - destroy
75// The first four are called as part of responding to client commands, but
76// start_read we want to call as soon as we're notified that the RPC was
77// successfully established (which happens concurrently in the network queue).
78// Serialization is achieved by using a private serial queue to operate the
79// call object.
80// Because add_metadata and invoke are called and return successfully before
81// any of the other methods is called, they don't need to use the queue.
82//
83// Furthermore, start_write and writes_done can only be called after the
84// WRITE_ACCEPTED event for any previous write is received. This is achieved by
85// pausing the requests writer immediately every time it writes a value, and
86// resuming it again when WRITE_ACCEPTED is received.
87//
88// Similarly, start_read can only be called after the READ event for any
89// previous read is received. This is easier to enforce, as we're writing the
90// received messages into the writeable: start_read is enqueued once upon receiving
91// the CLIENT_METADATA_READ event, and then once after receiving each READ
92// event.
93@implementation GRPCCall {
94 dispatch_queue_t _callQueue;
95
murgatroid9930b7d4e2015-04-24 10:36:43 -070096 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080097 dispatch_once_t _callAlreadyInvoked;
98
99 GRPCChannel *_channel;
100 GRPCCompletionQueue *_completionQueue;
101
102 // The C gRPC library has less guarantees on the ordering of events than we
103 // do. Particularly, in the face of errors, there's no ordering guarantee at
104 // all. This wrapper over our actual writeable ensures thread-safety and
105 // correct ordering.
106 GRPCDelegateWrapper *_responseWriteable;
107 id<GRXWriter> _requestWriter;
108}
109
110@synthesize state = _state;
111
112- (instancetype)init {
113 return [self initWithHost:nil method:nil requestsWriter:nil];
114}
115
116// Designated initializer
117- (instancetype)initWithHost:(NSString *)host
118 method:(GRPCMethodName *)method
119 requestsWriter:(id<GRXWriter>)requestWriter {
120 if (!host || !method) {
121 [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."];
122 }
123 // TODO(jcanizales): Throw if the requestWriter was already started.
124 if ((self = [super init])) {
125 static dispatch_once_t initialization;
126 dispatch_once(&initialization, ^{
127 grpc_init();
128 });
129
130 _completionQueue = [GRPCCompletionQueue completionQueue];
131
132 _channel = [GRPCChannel channelToHost:host];
murgatroid9930b7d4e2015-04-24 10:36:43 -0700133
134 _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel method:method.HTTP2Path host:host];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800135
136 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
137 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
138
139 _requestWriter = requestWriter;
140 }
141 return self;
142}
143
144#pragma mark Finish
145
146- (void)finishWithError:(NSError *)errorOrNil {
147 _requestWriter.state = GRXWriterStateFinished;
148 _requestWriter = nil;
149 if (errorOrNil) {
150 [_responseWriteable cancelWithError:errorOrNil];
151 } else {
152 [_responseWriteable enqueueSuccessfulCompletion];
153 }
154}
155
156- (void)cancelCall {
157 // Can be called from any thread, any number of times.
murgatroid9930b7d4e2015-04-24 10:36:43 -0700158 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800159}
160
161- (void)cancel {
162 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
163 code:GRPCErrorCodeCancelled
164 userInfo:nil]];
165 [self cancelCall];
166}
167
168- (void)dealloc {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800169 dispatch_async(_callQueue, ^{
murgatroid9930b7d4e2015-04-24 10:36:43 -0700170 _wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800171 });
172}
173
174#pragma mark Read messages
175
176// Only called from the call queue.
177// The handler will be called from the network queue.
murgatroid9930b7d4e2015-04-24 10:36:43 -0700178- (void)startReadWithHandler:(GRPCCompletionHandler)handler {
murgatroid9969927d62015-04-24 13:32:48 -0700179 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_RECV_MESSAGE): @YES} handleCompletion:handler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800180}
181
182// Called initially from the network queue once response headers are received,
183// then "recursively" from the responseWriteable queue after each response from the
184// server has been written.
185// If the call is currently paused, this is a noop. Restarting the call will invoke this
186// method.
187// TODO(jcanizales): Rename to readResponseIfNotPaused.
188- (void)startNextRead {
189 if (self.state == GRXWriterStatePaused) {
190 return;
191 }
192 __weak GRPCCall *weakSelf = self;
193 __weak GRPCDelegateWrapper *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700194
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800195 dispatch_async(_callQueue, ^{
murgatroid9969927d62015-04-24 13:32:48 -0700196 [weakSelf startReadWithHandler:^(NSDictionary *result) {
197 NSData *data = result[@(GRPC_OP_RECV_MESSAGE)];
murgatroid9930b7d4e2015-04-24 10:36:43 -0700198 if (data == [NSNull null]) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800199 return;
200 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800201 if (!data) {
202 // The app doesn't have enough memory to hold the server response. We
203 // don't want to throw, because the app shouldn't crash for a behavior
204 // that's on the hands of any server to have. Instead we finish and ask
205 // the server to cancel.
206 //
207 // TODO(jcanizales): No canonical code is appropriate for this situation
208 // (because it's just a client problem). Use another domain and an
209 // appropriately-documented code.
210 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
211 code:GRPCErrorCodeInternal
212 userInfo:nil]];
213 [weakSelf cancelCall];
214 return;
215 }
216 [weakWriteable enqueueMessage:data completionHandler:^{
217 [weakSelf startNextRead];
218 }];
219 }];
220 });
221}
222
223#pragma mark Send headers
224
murgatroid9969927d62015-04-24 13:32:48 -0700225// TODO(jcanizales): Rename to commitHeaders.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800226- (void)sendHeaders:(NSDictionary *)metadata {
murgatroid9969927d62015-04-24 13:32:48 -0700227 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_SEND_INITIAL_METADATA): metadata?:@{}} handleCompletion:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800228}
229
230#pragma mark GRXWriteable implementation
231
232// Only called from the call queue. The error handler will be called from the
233// network queue if the write didn't succeed.
234- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
235
236 __weak GRPCCall *weakSelf = self;
murgatroid9969927d62015-04-24 13:32:48 -0700237 GRPCCompletionHandler resumingHandler = ^(NSDictionary *result) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800238 // Resume the request writer (even in the case of error).
239 // TODO(jcanizales): No need to do it in the case of errors anymore?
240 GRPCCall *strongSelf = weakSelf;
241 if (strongSelf) {
242 strongSelf->_requestWriter.state = GRXWriterStateStarted;
243 }
244 };
murgatroid9969927d62015-04-24 13:32:48 -0700245 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_SEND_MESSAGE): message} handleCompletion:resumingHandler errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800246}
247
248- (void)didReceiveValue:(id)value {
249 // TODO(jcanizales): Throw/assert if value isn't NSData.
250
251 // Pause the input and only resume it when the C layer notifies us that writes
252 // can proceed.
253 _requestWriter.state = GRXWriterStatePaused;
254
255 __weak GRPCCall *weakSelf = self;
256 dispatch_async(_callQueue, ^{
257 [weakSelf writeMessage:value withErrorHandler:^{
258 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
259 code:GRPCErrorCodeInternal
260 userInfo:nil]];
261 }];
262 });
263}
264
265// Only called from the call queue. The error handler will be called from the
266// network queue if the requests stream couldn't be closed successfully.
267- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid9969927d62015-04-24 13:32:48 -0700268 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_SEND_CLOSE_FROM_CLIENT): @YES} handleCompletion:nil errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800269}
270
271- (void)didFinishWithError:(NSError *)errorOrNil {
272 if (errorOrNil) {
273 [self cancel];
274 } else {
275 __weak GRPCCall *weakSelf = self;
276 dispatch_async(_callQueue, ^{
277 [weakSelf finishRequestWithErrorHandler:^{
278 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
279 code:GRPCErrorCodeInternal
280 userInfo:nil]];
281 }];
282 });
283 }
284}
285
286#pragma mark Invoke
287
288// Both handlers will eventually be called, from the network queue. Writes can start immediately
289// after this.
290// The first one (metadataHandler), when the response headers are received.
291// The second one (completionHandler), whenever the RPC finishes for any reason.
murgatroid9930b7d4e2015-04-24 10:36:43 -0700292- (void)invokeCallWithMetadataHandler:(GRPCCompletionHandler)metadataHandler
293 completionHandler:(GRPCCompletionHandler)completionHandler {
murgatroid9969927d62015-04-24 13:32:48 -0700294 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_RECV_INITIAL_METADATA): @YES} handleCompletion:metadataHandler];
295 [_wrappedCall startBatchWithOperations:@{@(GRPC_OP_RECV_STATUS_ON_CLIENT): @YES} handleCompletion:completionHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800296}
297
298- (void)invokeCall {
299 __weak GRPCCall *weakSelf = self;
murgatroid9969927d62015-04-24 13:32:48 -0700300 [self invokeCallWithMetadataHandler:^(NSDictionary *result) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800301 // Response metadata received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800302 GRPCCall *strongSelf = weakSelf;
303 if (strongSelf) {
murgatroid9969927d62015-04-24 13:32:48 -0700304 strongSelf.responseMetadata = result[@(GRPC_OP_RECV_INITIAL_METADATA)];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800305 [strongSelf startNextRead];
306 }
murgatroid9969927d62015-04-24 13:32:48 -0700307 } completionHandler:^(NSDictionary *result) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800308 // TODO(jcanizales): Merge HTTP2 trailers into response metadata.
murgatroid9969927d62015-04-24 13:32:48 -0700309 id error = result[@(GRPC_OP_RECV_STATUS_ON_CLIENT)];
murgatroid9930b7d4e2015-04-24 10:36:43 -0700310 if (error == [NSNull null]) {
311 error = nil;
312 }
313 [weakSelf finishWithError:error];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800314 }];
315 // Now that the RPC has been initiated, request writes can start.
316 [_requestWriter startWithWriteable:self];
317}
318
319#pragma mark GRXWriter implementation
320
321- (void)startWithWriteable:(id<GRXWriteable>)writeable {
322 // The following produces a retain cycle self:_responseWriteable:self, which is only
323 // broken when didFinishWithError: is sent to the wrapped writeable.
324 // Care is taken not to retain self strongly in any of the blocks used in
325 // the implementation of GRPCCall, so that the life of the instance is
326 // determined by this retain cycle.
327 _responseWriteable = [[GRPCDelegateWrapper alloc] initWithWriteable:writeable writer:self];
328 [self sendHeaders:_requestMetadata];
329 [self invokeCall];
330}
331
332- (void)setState:(GRXWriterState)newState {
333 // Manual transitions are only allowed from the started or paused states.
334 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
335 return;
336 }
337
338 switch (newState) {
339 case GRXWriterStateFinished:
340 _state = newState;
341 // Per GRXWriter's contract, setting the state to Finished manually
342 // means one doesn't wish the writeable to be messaged anymore.
343 [_responseWriteable cancelSilently];
344 _responseWriteable = nil;
345 return;
346 case GRXWriterStatePaused:
347 _state = newState;
348 return;
349 case GRXWriterStateStarted:
350 if (_state == GRXWriterStatePaused) {
351 _state = newState;
352 [self startNextRead];
353 }
354 return;
355 case GRXWriterStateNotStarted:
356 return;
357 }
358}
359@end