blob: 34d1221a1bacaeb2863a85c5bf7e899173531663 [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:
murgatroid99b5c076f2015-04-27 17:25:36 -070069// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080070// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080071//
murgatroid99b5c076f2015-04-27 17:25:36 -070072// start_batch with a SEND_MESSAGE argument can only be called after the
73// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080074// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070075// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080076//
murgatroid99b5c076f2015-04-27 17:25:36 -070077// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
78// the OP_COMPLETE event for any previous read is received.This is easier to
79// enforce, as we're writing the received messages into the writeable:
80// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
81// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
82// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080083@implementation GRPCCall {
84 dispatch_queue_t _callQueue;
85
murgatroid9930b7d4e2015-04-24 10:36:43 -070086 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080087 dispatch_once_t _callAlreadyInvoked;
88
89 GRPCChannel *_channel;
90 GRPCCompletionQueue *_completionQueue;
91
92 // The C gRPC library has less guarantees on the ordering of events than we
93 // do. Particularly, in the face of errors, there's no ordering guarantee at
94 // all. This wrapper over our actual writeable ensures thread-safety and
95 // correct ordering.
96 GRPCDelegateWrapper *_responseWriteable;
97 id<GRXWriter> _requestWriter;
98}
99
100@synthesize state = _state;
101
102- (instancetype)init {
103 return [self initWithHost:nil method:nil requestsWriter:nil];
104}
105
106// Designated initializer
107- (instancetype)initWithHost:(NSString *)host
108 method:(GRPCMethodName *)method
109 requestsWriter:(id<GRXWriter>)requestWriter {
110 if (!host || !method) {
111 [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."];
112 }
113 // TODO(jcanizales): Throw if the requestWriter was already started.
114 if ((self = [super init])) {
115 static dispatch_once_t initialization;
116 dispatch_once(&initialization, ^{
117 grpc_init();
118 });
119
120 _completionQueue = [GRPCCompletionQueue completionQueue];
121
122 _channel = [GRPCChannel channelToHost:host];
murgatroid9930b7d4e2015-04-24 10:36:43 -0700123
124 _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel method:method.HTTP2Path host:host];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800125
126 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
127 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
128
129 _requestWriter = requestWriter;
130 }
131 return self;
132}
133
134#pragma mark Finish
135
136- (void)finishWithError:(NSError *)errorOrNil {
137 _requestWriter.state = GRXWriterStateFinished;
138 _requestWriter = nil;
139 if (errorOrNil) {
140 [_responseWriteable cancelWithError:errorOrNil];
141 } else {
142 [_responseWriteable enqueueSuccessfulCompletion];
143 }
144}
145
146- (void)cancelCall {
147 // Can be called from any thread, any number of times.
murgatroid9930b7d4e2015-04-24 10:36:43 -0700148 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800149}
150
151- (void)cancel {
152 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
153 code:GRPCErrorCodeCancelled
154 userInfo:nil]];
155 [self cancelCall];
156}
157
158- (void)dealloc {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800159 dispatch_async(_callQueue, ^{
murgatroid9930b7d4e2015-04-24 10:36:43 -0700160 _wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800161 });
162}
163
164#pragma mark Read messages
165
166// Only called from the call queue.
167// The handler will be called from the network queue.
murgatroid9954e93d42015-04-27 09:29:49 -0700168- (void)startReadWithHandler:(void(^)(NSData *))handler {
169 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800170}
171
172// Called initially from the network queue once response headers are received,
173// then "recursively" from the responseWriteable queue after each response from the
174// server has been written.
175// If the call is currently paused, this is a noop. Restarting the call will invoke this
176// method.
177// TODO(jcanizales): Rename to readResponseIfNotPaused.
178- (void)startNextRead {
179 if (self.state == GRXWriterStatePaused) {
180 return;
181 }
182 __weak GRPCCall *weakSelf = self;
183 __weak GRPCDelegateWrapper *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700184
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800185 dispatch_async(_callQueue, ^{
murgatroid9954e93d42015-04-27 09:29:49 -0700186 [weakSelf startReadWithHandler:^(NSData *data) {
187 if (data == nil) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800188 return;
189 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800190 if (!data) {
191 // The app doesn't have enough memory to hold the server response. We
192 // don't want to throw, because the app shouldn't crash for a behavior
193 // that's on the hands of any server to have. Instead we finish and ask
194 // the server to cancel.
195 //
196 // TODO(jcanizales): No canonical code is appropriate for this situation
197 // (because it's just a client problem). Use another domain and an
198 // appropriately-documented code.
199 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
200 code:GRPCErrorCodeInternal
201 userInfo:nil]];
202 [weakSelf cancelCall];
203 return;
204 }
205 [weakWriteable enqueueMessage:data completionHandler:^{
206 [weakSelf startNextRead];
207 }];
208 }];
209 });
210}
211
212#pragma mark Send headers
213
murgatroid9969927d62015-04-24 13:32:48 -0700214// TODO(jcanizales): Rename to commitHeaders.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800215- (void)sendHeaders:(NSDictionary *)metadata {
murgatroid99dcde9702015-04-27 11:12:57 -0700216 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] initWithMetadata:metadata ?: @{} handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800217}
218
219#pragma mark GRXWriteable implementation
220
221// Only called from the call queue. The error handler will be called from the
222// network queue if the write didn't succeed.
223- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
224
225 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700226 void(^resumingHandler)(void) = ^{
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800227 // Resume the request writer (even in the case of error).
228 // TODO(jcanizales): No need to do it in the case of errors anymore?
229 GRPCCall *strongSelf = weakSelf;
230 if (strongSelf) {
231 strongSelf->_requestWriter.state = GRXWriterStateStarted;
232 }
233 };
murgatroid9954e93d42015-04-27 09:29:49 -0700234 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message handler:resumingHandler]] errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800235}
236
237- (void)didReceiveValue:(id)value {
238 // TODO(jcanizales): Throw/assert if value isn't NSData.
239
240 // Pause the input and only resume it when the C layer notifies us that writes
241 // can proceed.
242 _requestWriter.state = GRXWriterStatePaused;
243
244 __weak GRPCCall *weakSelf = self;
245 dispatch_async(_callQueue, ^{
246 [weakSelf writeMessage:value withErrorHandler:^{
247 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
248 code:GRPCErrorCodeInternal
249 userInfo:nil]];
250 }];
251 });
252}
253
254// Only called from the call queue. The error handler will be called from the
255// network queue if the requests stream couldn't be closed successfully.
256- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid99e08a9c02015-04-27 12:31:36 -0700257 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]] errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800258}
259
260- (void)didFinishWithError:(NSError *)errorOrNil {
261 if (errorOrNil) {
262 [self cancel];
263 } else {
264 __weak GRPCCall *weakSelf = self;
265 dispatch_async(_callQueue, ^{
266 [weakSelf finishRequestWithErrorHandler:^{
267 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
268 code:GRPCErrorCodeInternal
269 userInfo:nil]];
270 }];
271 });
272 }
273}
274
275#pragma mark Invoke
276
277// Both handlers will eventually be called, from the network queue. Writes can start immediately
278// after this.
279// The first one (metadataHandler), when the response headers are received.
280// The second one (completionHandler), whenever the RPC finishes for any reason.
murgatroid9954e93d42015-04-27 09:29:49 -0700281- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler
282 completionHandler:(void(^)(NSError *))completionHandler {
283 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc] initWithHandler:metadataHandler]]];
284 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc] initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800285}
286
287- (void)invokeCall {
288 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700289 [self invokeCallWithMetadataHandler:^(NSDictionary *metadata) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800290 // Response metadata received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800291 GRPCCall *strongSelf = weakSelf;
292 if (strongSelf) {
murgatroid9954e93d42015-04-27 09:29:49 -0700293 strongSelf.responseMetadata = metadata;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800294 [strongSelf startNextRead];
295 }
murgatroid9954e93d42015-04-27 09:29:49 -0700296 } completionHandler:^(NSError *error) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800297 // TODO(jcanizales): Merge HTTP2 trailers into response metadata.
murgatroid9930b7d4e2015-04-24 10:36:43 -0700298 [weakSelf finishWithError:error];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800299 }];
300 // Now that the RPC has been initiated, request writes can start.
301 [_requestWriter startWithWriteable:self];
302}
303
304#pragma mark GRXWriter implementation
305
306- (void)startWithWriteable:(id<GRXWriteable>)writeable {
307 // The following produces a retain cycle self:_responseWriteable:self, which is only
308 // broken when didFinishWithError: is sent to the wrapped writeable.
309 // Care is taken not to retain self strongly in any of the blocks used in
310 // the implementation of GRPCCall, so that the life of the instance is
311 // determined by this retain cycle.
312 _responseWriteable = [[GRPCDelegateWrapper alloc] initWithWriteable:writeable writer:self];
313 [self sendHeaders:_requestMetadata];
314 [self invokeCall];
315}
316
317- (void)setState:(GRXWriterState)newState {
318 // Manual transitions are only allowed from the started or paused states.
319 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
320 return;
321 }
322
323 switch (newState) {
324 case GRXWriterStateFinished:
325 _state = newState;
326 // Per GRXWriter's contract, setting the state to Finished manually
327 // means one doesn't wish the writeable to be messaged anymore.
328 [_responseWriteable cancelSilently];
329 _responseWriteable = nil;
330 return;
331 case GRXWriterStatePaused:
332 _state = newState;
333 return;
334 case GRXWriterStateStarted:
335 if (_state == GRXWriterStatePaused) {
336 _state = newState;
337 [self startNextRead];
338 }
339 return;
340 case GRXWriterStateNotStarted:
341 return;
342 }
343}
344@end