blob: 9435bf2b35cb88771240485b97162c24b21402b2 [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>
Jorge Canizales59bb9d72015-06-22 19:04:15 -070037#include <grpc/support/time.h>
Jorge Canizales35f003b2015-07-17 21:14:36 -070038#import <RxLibrary/GRXConcurrentWriteable.h>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080039
Jorge Canizales5e0efd92015-02-17 18:23:58 -080040#import "private/GRPCChannel.h"
41#import "private/GRPCCompletionQueue.h"
murgatroid9969927d62015-04-24 13:32:48 -070042#import "private/GRPCWrappedCall.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080043#import "private/NSData+GRPC.h"
44#import "private/NSDictionary+GRPC.h"
45#import "private/NSError+GRPC.h"
46
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070047NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
48
Jorge Canizales5e0efd92015-02-17 18:23:58 -080049@interface GRPCCall () <GRXWriteable>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080050@end
51
52// The following methods of a C gRPC call object aren't reentrant, and thus
53// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070054// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080055// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080056//
murgatroid99b5c076f2015-04-27 17:25:36 -070057// start_batch with a SEND_MESSAGE argument can only be called after the
58// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080059// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070060// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080061//
murgatroid99b5c076f2015-04-27 17:25:36 -070062// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
63// the OP_COMPLETE event for any previous read is received.This is easier to
64// enforce, as we're writing the received messages into the writeable:
65// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
66// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
67// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080068@implementation GRPCCall {
69 dispatch_queue_t _callQueue;
70
murgatroid9930b7d4e2015-04-24 10:36:43 -070071 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080072 dispatch_once_t _callAlreadyInvoked;
73
74 GRPCChannel *_channel;
75 GRPCCompletionQueue *_completionQueue;
76
77 // The C gRPC library has less guarantees on the ordering of events than we
78 // do. Particularly, in the face of errors, there's no ordering guarantee at
79 // all. This wrapper over our actual writeable ensures thread-safety and
80 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070081 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales56047122015-07-17 12:18:08 -070082 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070083
Jorge Canizales6531b2b2015-07-18 00:19:14 -070084 // To create a retain cycle when a call is started, up until it finishes. See
85 // |startWithWriteable:| and |finishWithError:|.
86 GRPCCall *_self;
87
Jorge Canizales544963e2015-06-12 19:46:27 -070088 NSMutableDictionary *_requestMetadata;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070089 NSMutableDictionary *_responseMetadata;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080090}
91
92@synthesize state = _state;
93
94- (instancetype)init {
Jorge Canizalesbe808e32015-07-04 14:37:58 -070095 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -080096}
97
98// Designated initializer
99- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700100 path:(NSString *)path
Jorge Canizales56047122015-07-17 12:18:08 -0700101 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700102 if (!host || !path) {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800103 [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."];
104 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700105 if (requestWriter.state != GRXWriterStateNotStarted) {
106 [NSException raise:NSInvalidArgumentException format:@"The requests writer can't be already started."];
107 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800108 if ((self = [super init])) {
109 static dispatch_once_t initialization;
110 dispatch_once(&initialization, ^{
111 grpc_init();
112 });
113
114 _completionQueue = [GRPCCompletionQueue completionQueue];
115
116 _channel = [GRPCChannel channelToHost:host];
murgatroid99b56609c2015-04-28 16:41:11 -0700117
118 _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700119 path:path
murgatroid99b56609c2015-04-28 16:41:11 -0700120 host:host];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800121
122 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
123 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
124
125 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700126
127 _requestMetadata = [NSMutableDictionary dictionary];
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700128 _responseMetadata = [NSMutableDictionary dictionary];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800129 }
130 return self;
131}
132
Jorge Canizales544963e2015-06-12 19:46:27 -0700133#pragma mark Metadata
134
135- (NSMutableDictionary *)requestMetadata {
136 return _requestMetadata;
137}
138
139- (void)setRequestMetadata:(NSDictionary *)requestMetadata {
140 _requestMetadata = [NSMutableDictionary dictionaryWithDictionary:requestMetadata];
141}
142
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700143- (NSDictionary *)responseMetadata {
144 return _responseMetadata;
145}
146
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800147#pragma mark Finish
148
149- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700150 // If the call isn't retained anywhere else, it can be deallocated now.
151 _self = nil;
152
153 // If there were still request messages coming, stop them.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800154 _requestWriter.state = GRXWriterStateFinished;
155 _requestWriter = nil;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700156
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800157 if (errorOrNil) {
158 [_responseWriteable cancelWithError:errorOrNil];
159 } else {
160 [_responseWriteable enqueueSuccessfulCompletion];
161 }
162}
163
164- (void)cancelCall {
165 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700166 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800167}
168
169- (void)cancel {
170 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
171 code:GRPCErrorCodeCancelled
172 userInfo:nil]];
173 [self cancelCall];
174}
175
176- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700177 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800178 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700179 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800180 });
181}
182
183#pragma mark Read messages
184
185// Only called from the call queue.
186// The handler will be called from the network queue.
murgatroid996cc46802015-04-28 09:35:48 -0700187- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700188 // TODO(jcanizales): Add error handlers for async failures
murgatroid9954e93d42015-04-27 09:29:49 -0700189 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800190}
191
192// Called initially from the network queue once response headers are received,
193// then "recursively" from the responseWriteable queue after each response from the
194// server has been written.
195// If the call is currently paused, this is a noop. Restarting the call will invoke this
196// method.
197// TODO(jcanizales): Rename to readResponseIfNotPaused.
198- (void)startNextRead {
199 if (self.state == GRXWriterStatePaused) {
200 return;
201 }
202 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700203 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700204
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800205 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700206 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
207 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700208 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800209 return;
210 }
murgatroid996cc46802015-04-28 09:35:48 -0700211 NSData *data = [NSData grpc_dataWithByteBuffer:message];
212 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800213 if (!data) {
214 // The app doesn't have enough memory to hold the server response. We
215 // don't want to throw, because the app shouldn't crash for a behavior
216 // that's on the hands of any server to have. Instead we finish and ask
217 // the server to cancel.
218 //
219 // TODO(jcanizales): No canonical code is appropriate for this situation
220 // (because it's just a client problem). Use another domain and an
221 // appropriately-documented code.
222 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
223 code:GRPCErrorCodeInternal
224 userInfo:nil]];
225 [weakSelf cancelCall];
226 return;
227 }
Jorge Canizales4c6f7782015-07-17 23:13:36 -0700228 [weakWriteable enqueueValue:data completionHandler:^{
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800229 [weakSelf startNextRead];
230 }];
231 }];
232 });
233}
234
235#pragma mark Send headers
236
murgatroid9969927d62015-04-24 13:32:48 -0700237// TODO(jcanizales): Rename to commitHeaders.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800238- (void)sendHeaders:(NSDictionary *)metadata {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700239 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700240 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
241 initWithMetadata:metadata ?: @{} handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800242}
243
244#pragma mark GRXWriteable implementation
245
246// Only called from the call queue. The error handler will be called from the
247// network queue if the write didn't succeed.
248- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
249
250 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700251 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700252 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800253 GRPCCall *strongSelf = weakSelf;
254 if (strongSelf) {
255 strongSelf->_requestWriter.state = GRXWriterStateStarted;
256 }
257 };
murgatroid996cc46802015-04-28 09:35:48 -0700258 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc]
259 initWithMessage:message
260 handler:resumingHandler]] errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800261}
262
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700263- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800264 // TODO(jcanizales): Throw/assert if value isn't NSData.
265
266 // Pause the input and only resume it when the C layer notifies us that writes
267 // can proceed.
268 _requestWriter.state = GRXWriterStatePaused;
269
270 __weak GRPCCall *weakSelf = self;
271 dispatch_async(_callQueue, ^{
272 [weakSelf writeMessage:value withErrorHandler:^{
273 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
274 code:GRPCErrorCodeInternal
275 userInfo:nil]];
276 }];
277 });
278}
279
280// Only called from the call queue. The error handler will be called from the
281// network queue if the requests stream couldn't be closed successfully.
282- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid996cc46802015-04-28 09:35:48 -0700283 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
284 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800285}
286
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700287- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700288 _requestWriter = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800289 if (errorOrNil) {
290 [self cancel];
291 } else {
292 __weak GRPCCall *weakSelf = self;
293 dispatch_async(_callQueue, ^{
294 [weakSelf finishRequestWithErrorHandler:^{
295 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
296 code:GRPCErrorCodeInternal
297 userInfo:nil]];
298 }];
299 });
300 }
301}
302
303#pragma mark Invoke
304
305// Both handlers will eventually be called, from the network queue. Writes can start immediately
306// after this.
307// The first one (metadataHandler), when the response headers are received.
308// The second one (completionHandler), whenever the RPC finishes for any reason.
murgatroid9954e93d42015-04-27 09:29:49 -0700309- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700310 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700311 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700312 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
313 initWithHandler:metadataHandler]]];
314 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
315 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800316}
317
318- (void)invokeCall {
319 __weak GRPCCall *weakSelf = self;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700320 [self invokeCallWithMetadataHandler:^(NSDictionary *headers) {
321 // Response headers received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800322 GRPCCall *strongSelf = weakSelf;
323 if (strongSelf) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700324 [strongSelf->_responseMetadata addEntriesFromDictionary:headers];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800325 [strongSelf startNextRead];
326 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700327 } completionHandler:^(NSError *error, NSDictionary *trailers) {
328 GRPCCall *strongSelf = weakSelf;
329 if (strongSelf) {
330 [strongSelf->_responseMetadata addEntriesFromDictionary:trailers];
331
Jorge Canizalesc58a1102015-06-15 19:03:41 -0700332 if (error) {
333 NSMutableDictionary *userInfo =
334 [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
335 userInfo[kGRPCStatusMetadataKey] = strongSelf->_responseMetadata;
336 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
337 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700338 [strongSelf finishWithError:error];
339 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800340 }];
341 // Now that the RPC has been initiated, request writes can start.
342 [_requestWriter startWithWriteable:self];
343}
344
345#pragma mark GRXWriter implementation
346
347- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700348 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
349 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
350 // before being autoreleased).
351 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
352 // that the life of the instance is determined by this retain cycle.
353 _self = self;
354
355 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800356 [self sendHeaders:_requestMetadata];
357 [self invokeCall];
358}
359
360- (void)setState:(GRXWriterState)newState {
361 // Manual transitions are only allowed from the started or paused states.
362 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
363 return;
364 }
365
366 switch (newState) {
367 case GRXWriterStateFinished:
368 _state = newState;
369 // Per GRXWriter's contract, setting the state to Finished manually
370 // means one doesn't wish the writeable to be messaged anymore.
371 [_responseWriteable cancelSilently];
372 _responseWriteable = nil;
373 return;
374 case GRXWriterStatePaused:
375 _state = newState;
376 return;
377 case GRXWriterStateStarted:
378 if (_state == GRXWriterStatePaused) {
379 _state = newState;
380 [self startNextRead];
381 }
382 return;
383 case GRXWriterStateNotStarted:
384 return;
385 }
386}
387@end