blob: 687932c9381b9ab096fec7aea9964b49a1ec274e [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
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070049NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
50
Jorge Canizales5e0efd92015-02-17 18:23:58 -080051@interface GRPCCall () <GRXWriteable>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080052@end
53
54// The following methods of a C gRPC call object aren't reentrant, and thus
55// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070056// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080057// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080058//
murgatroid99b5c076f2015-04-27 17:25:36 -070059// start_batch with a SEND_MESSAGE argument can only be called after the
60// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080061// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070062// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080063//
murgatroid99b5c076f2015-04-27 17:25:36 -070064// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
65// the OP_COMPLETE event for any previous read is received.This is easier to
66// enforce, as we're writing the received messages into the writeable:
67// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
68// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
69// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080070@implementation GRPCCall {
71 dispatch_queue_t _callQueue;
72
murgatroid9930b7d4e2015-04-24 10:36:43 -070073 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080074 dispatch_once_t _callAlreadyInvoked;
75
76 GRPCChannel *_channel;
77 GRPCCompletionQueue *_completionQueue;
78
79 // The C gRPC library has less guarantees on the ordering of events than we
80 // do. Particularly, in the face of errors, there's no ordering guarantee at
81 // all. This wrapper over our actual writeable ensures thread-safety and
82 // correct ordering.
83 GRPCDelegateWrapper *_responseWriteable;
84 id<GRXWriter> _requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070085
86 NSMutableDictionary *_requestMetadata;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070087 NSMutableDictionary *_responseMetadata;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080088}
89
90@synthesize state = _state;
91
92- (instancetype)init {
93 return [self initWithHost:nil method:nil requestsWriter:nil];
94}
95
96// Designated initializer
97- (instancetype)initWithHost:(NSString *)host
98 method:(GRPCMethodName *)method
99 requestsWriter:(id<GRXWriter>)requestWriter {
100 if (!host || !method) {
101 [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."];
102 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700103 if (requestWriter.state != GRXWriterStateNotStarted) {
104 [NSException raise:NSInvalidArgumentException format:@"The requests writer can't be already started."];
105 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800106 if ((self = [super init])) {
107 static dispatch_once_t initialization;
108 dispatch_once(&initialization, ^{
109 grpc_init();
110 });
111
112 _completionQueue = [GRPCCompletionQueue completionQueue];
113
114 _channel = [GRPCChannel channelToHost:host];
murgatroid99b56609c2015-04-28 16:41:11 -0700115
116 _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel
117 method:method.HTTP2Path
118 host:host];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800119
120 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
121 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
122
123 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700124
125 _requestMetadata = [NSMutableDictionary dictionary];
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700126 _responseMetadata = [NSMutableDictionary dictionary];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800127 }
128 return self;
129}
130
Jorge Canizales544963e2015-06-12 19:46:27 -0700131#pragma mark Metadata
132
133- (NSMutableDictionary *)requestMetadata {
134 return _requestMetadata;
135}
136
137- (void)setRequestMetadata:(NSDictionary *)requestMetadata {
138 _requestMetadata = [NSMutableDictionary dictionaryWithDictionary:requestMetadata];
139}
140
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700141- (NSDictionary *)responseMetadata {
142 return _responseMetadata;
143}
144
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800145#pragma mark Finish
146
147- (void)finishWithError:(NSError *)errorOrNil {
148 _requestWriter.state = GRXWriterStateFinished;
149 _requestWriter = nil;
150 if (errorOrNil) {
151 [_responseWriteable cancelWithError:errorOrNil];
152 } else {
153 [_responseWriteable enqueueSuccessfulCompletion];
154 }
155}
156
157- (void)cancelCall {
158 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700159 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800160}
161
162- (void)cancel {
163 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
164 code:GRPCErrorCodeCancelled
165 userInfo:nil]];
166 [self cancelCall];
167}
168
169- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700170 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800171 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700172 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800173 });
174}
175
176#pragma mark Read messages
177
178// Only called from the call queue.
179// The handler will be called from the network queue.
murgatroid996cc46802015-04-28 09:35:48 -0700180- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700181 // TODO(jcanizales): Add error handlers for async failures
murgatroid9954e93d42015-04-27 09:29:49 -0700182 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800183}
184
185// Called initially from the network queue once response headers are received,
186// then "recursively" from the responseWriteable queue after each response from the
187// server has been written.
188// If the call is currently paused, this is a noop. Restarting the call will invoke this
189// method.
190// TODO(jcanizales): Rename to readResponseIfNotPaused.
191- (void)startNextRead {
192 if (self.state == GRXWriterStatePaused) {
193 return;
194 }
195 __weak GRPCCall *weakSelf = self;
196 __weak GRPCDelegateWrapper *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700197
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800198 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700199 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
200 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700201 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800202 return;
203 }
murgatroid996cc46802015-04-28 09:35:48 -0700204 NSData *data = [NSData grpc_dataWithByteBuffer:message];
205 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800206 if (!data) {
207 // The app doesn't have enough memory to hold the server response. We
208 // don't want to throw, because the app shouldn't crash for a behavior
209 // that's on the hands of any server to have. Instead we finish and ask
210 // the server to cancel.
211 //
212 // TODO(jcanizales): No canonical code is appropriate for this situation
213 // (because it's just a client problem). Use another domain and an
214 // appropriately-documented code.
215 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
216 code:GRPCErrorCodeInternal
217 userInfo:nil]];
218 [weakSelf cancelCall];
219 return;
220 }
221 [weakWriteable enqueueMessage:data completionHandler:^{
222 [weakSelf startNextRead];
223 }];
224 }];
225 });
226}
227
228#pragma mark Send headers
229
murgatroid9969927d62015-04-24 13:32:48 -0700230// TODO(jcanizales): Rename to commitHeaders.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800231- (void)sendHeaders:(NSDictionary *)metadata {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700232 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700233 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
234 initWithMetadata:metadata ?: @{} handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800235}
236
237#pragma mark GRXWriteable implementation
238
239// Only called from the call queue. The error handler will be called from the
240// network queue if the write didn't succeed.
241- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
242
243 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700244 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700245 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800246 GRPCCall *strongSelf = weakSelf;
247 if (strongSelf) {
248 strongSelf->_requestWriter.state = GRXWriterStateStarted;
249 }
250 };
murgatroid996cc46802015-04-28 09:35:48 -0700251 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc]
252 initWithMessage:message
253 handler:resumingHandler]] errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800254}
255
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700256- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800257 // TODO(jcanizales): Throw/assert if value isn't NSData.
258
259 // Pause the input and only resume it when the C layer notifies us that writes
260 // can proceed.
261 _requestWriter.state = GRXWriterStatePaused;
262
263 __weak GRPCCall *weakSelf = self;
264 dispatch_async(_callQueue, ^{
265 [weakSelf writeMessage:value withErrorHandler:^{
266 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
267 code:GRPCErrorCodeInternal
268 userInfo:nil]];
269 }];
270 });
271}
272
273// Only called from the call queue. The error handler will be called from the
274// network queue if the requests stream couldn't be closed successfully.
275- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid996cc46802015-04-28 09:35:48 -0700276 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
277 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800278}
279
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700280- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800281 if (errorOrNil) {
282 [self cancel];
283 } else {
284 __weak GRPCCall *weakSelf = self;
285 dispatch_async(_callQueue, ^{
286 [weakSelf finishRequestWithErrorHandler:^{
287 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
288 code:GRPCErrorCodeInternal
289 userInfo:nil]];
290 }];
291 });
292 }
293}
294
295#pragma mark Invoke
296
297// Both handlers will eventually be called, from the network queue. Writes can start immediately
298// after this.
299// The first one (metadataHandler), when the response headers are received.
300// The second one (completionHandler), whenever the RPC finishes for any reason.
murgatroid9954e93d42015-04-27 09:29:49 -0700301- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700302 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700303 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700304 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
305 initWithHandler:metadataHandler]]];
306 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
307 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800308}
309
310- (void)invokeCall {
311 __weak GRPCCall *weakSelf = self;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700312 [self invokeCallWithMetadataHandler:^(NSDictionary *headers) {
313 // Response headers received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800314 GRPCCall *strongSelf = weakSelf;
315 if (strongSelf) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700316 [strongSelf->_responseMetadata addEntriesFromDictionary:headers];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800317 [strongSelf startNextRead];
318 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700319 } completionHandler:^(NSError *error, NSDictionary *trailers) {
320 GRPCCall *strongSelf = weakSelf;
321 if (strongSelf) {
322 [strongSelf->_responseMetadata addEntriesFromDictionary:trailers];
323
324 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
325 userInfo[kGRPCStatusMetadataKey] = strongSelf->_responseMetadata;
326 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
327 [strongSelf finishWithError:error];
328 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800329 }];
330 // Now that the RPC has been initiated, request writes can start.
331 [_requestWriter startWithWriteable:self];
332}
333
334#pragma mark GRXWriter implementation
335
336- (void)startWithWriteable:(id<GRXWriteable>)writeable {
337 // The following produces a retain cycle self:_responseWriteable:self, which is only
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700338 // broken when writesFinishedWithError: is sent to the wrapped writeable.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800339 // Care is taken not to retain self strongly in any of the blocks used in
340 // the implementation of GRPCCall, so that the life of the instance is
341 // determined by this retain cycle.
342 _responseWriteable = [[GRPCDelegateWrapper alloc] initWithWriteable:writeable writer:self];
343 [self sendHeaders:_requestMetadata];
344 [self invokeCall];
345}
346
347- (void)setState:(GRXWriterState)newState {
348 // Manual transitions are only allowed from the started or paused states.
349 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
350 return;
351 }
352
353 switch (newState) {
354 case GRXWriterStateFinished:
355 _state = newState;
356 // Per GRXWriter's contract, setting the state to Finished manually
357 // means one doesn't wish the writeable to be messaged anymore.
358 [_responseWriteable cancelSilently];
359 _responseWriteable = nil;
360 return;
361 case GRXWriterStatePaused:
362 _state = newState;
363 return;
364 case GRXWriterStateStarted:
365 if (_state == GRXWriterStatePaused) {
366 _state = newState;
367 [self startNextRead];
368 }
369 return;
370 case GRXWriterStateNotStarted:
371 return;
372 }
373}
374@end