blob: 16abd0fadf1b8ec7d4e623f91908026cb5a418aa [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
murgatroid9969927d62015-04-24 13:32:48 -070040#import "private/GRPCWrappedCall.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080041#import "private/NSData+GRPC.h"
42#import "private/NSDictionary+GRPC.h"
43#import "private/NSError+GRPC.h"
44
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070045NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
46
Jorge Canizales5e0efd92015-02-17 18:23:58 -080047@interface GRPCCall () <GRXWriteable>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080048@end
49
50// The following methods of a C gRPC call object aren't reentrant, and thus
51// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070052// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080053// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080054//
murgatroid99b5c076f2015-04-27 17:25:36 -070055// start_batch with a SEND_MESSAGE argument can only be called after the
56// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080057// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070058// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080059//
murgatroid99b5c076f2015-04-27 17:25:36 -070060// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
61// the OP_COMPLETE event for any previous read is received.This is easier to
62// enforce, as we're writing the received messages into the writeable:
63// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
64// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
65// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080066@implementation GRPCCall {
67 dispatch_queue_t _callQueue;
68
murgatroid9930b7d4e2015-04-24 10:36:43 -070069 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080070 dispatch_once_t _callAlreadyInvoked;
71
Jorge Canizales5e0efd92015-02-17 18:23:58 -080072 // The C gRPC library has less guarantees on the ordering of events than we
73 // do. Particularly, in the face of errors, there's no ordering guarantee at
74 // all. This wrapper over our actual writeable ensures thread-safety and
75 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070076 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales238ad782015-08-07 23:11:29 -070077
78 // The network thread wants the requestWriter to resume (when the server is ready for more input),
79 // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
80 // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
81 // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
82 // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
83 // pause the writer immediately on writeValue:, so we need our locking to be recursive.
Jorge Canizales56047122015-07-17 12:18:08 -070084 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070085
Jorge Canizales6531b2b2015-07-18 00:19:14 -070086 // To create a retain cycle when a call is started, up until it finishes. See
87 // |startWithWriteable:| and |finishWithError:|.
88 GRPCCall *_self;
89
Jorge Canizales544963e2015-06-12 19:46:27 -070090 NSMutableDictionary *_requestMetadata;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070091 NSMutableDictionary *_responseMetadata;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080092}
93
94@synthesize state = _state;
95
96- (instancetype)init {
Jorge Canizalesbe808e32015-07-04 14:37:58 -070097 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -080098}
99
100// Designated initializer
101- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700102 path:(NSString *)path
Jorge Canizales56047122015-07-17 12:18:08 -0700103 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700104 if (!host || !path) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700105 [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800106 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700107 if (requestWriter.state != GRXWriterStateNotStarted) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700108 [NSException raise:NSInvalidArgumentException
109 format:@"The requests writer can't be already started."];
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700110 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800111 if ((self = [super init])) {
Jorge Canizales3a5253e2015-07-31 23:48:56 -0700112 _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:host path:path];
Jorge Canizales8c5318a2015-08-05 18:50:08 -0700113 if (!_wrappedCall) {
114 return nil;
115 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800116
117 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
118 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
119
120 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700121
122 _requestMetadata = [NSMutableDictionary dictionary];
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700123 _responseMetadata = [NSMutableDictionary dictionary];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800124 }
125 return self;
126}
127
Jorge Canizales544963e2015-06-12 19:46:27 -0700128#pragma mark Metadata
129
130- (NSMutableDictionary *)requestMetadata {
131 return _requestMetadata;
132}
133
134- (void)setRequestMetadata:(NSDictionary *)requestMetadata {
135 _requestMetadata = [NSMutableDictionary dictionaryWithDictionary:requestMetadata];
136}
137
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700138- (NSDictionary *)responseMetadata {
139 return _responseMetadata;
140}
141
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800142#pragma mark Finish
143
144- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700145 // If the call isn't retained anywhere else, it can be deallocated now.
146 _self = nil;
147
148 // If there were still request messages coming, stop them.
Jorge Canizales238ad782015-08-07 23:11:29 -0700149 @synchronized(_requestWriter) {
150 _requestWriter.state = GRXWriterStateFinished;
151 _requestWriter = nil;
152 }
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700153
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800154 if (errorOrNil) {
155 [_responseWriteable cancelWithError:errorOrNil];
156 } else {
157 [_responseWriteable enqueueSuccessfulCompletion];
158 }
159}
160
161- (void)cancelCall {
162 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700163 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800164}
165
166- (void)cancel {
167 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
168 code:GRPCErrorCodeCancelled
169 userInfo:nil]];
170 [self cancelCall];
171}
172
173- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700174 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800175 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700176 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800177 });
178}
179
180#pragma mark Read messages
181
182// Only called from the call queue.
183// The handler will be called from the network queue.
murgatroid996cc46802015-04-28 09:35:48 -0700184- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700185 // TODO(jcanizales): Add error handlers for async failures
murgatroid9954e93d42015-04-27 09:29:49 -0700186 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800187}
188
189// Called initially from the network queue once response headers are received,
190// then "recursively" from the responseWriteable queue after each response from the
191// server has been written.
192// If the call is currently paused, this is a noop. Restarting the call will invoke this
193// method.
194// TODO(jcanizales): Rename to readResponseIfNotPaused.
195- (void)startNextRead {
196 if (self.state == GRXWriterStatePaused) {
197 return;
198 }
199 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700200 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700201
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800202 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700203 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
204 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700205 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800206 return;
207 }
murgatroid996cc46802015-04-28 09:35:48 -0700208 NSData *data = [NSData grpc_dataWithByteBuffer:message];
209 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800210 if (!data) {
211 // The app doesn't have enough memory to hold the server response. We
212 // don't want to throw, because the app shouldn't crash for a behavior
213 // that's on the hands of any server to have. Instead we finish and ask
214 // the server to cancel.
215 //
216 // TODO(jcanizales): No canonical code is appropriate for this situation
217 // (because it's just a client problem). Use another domain and an
218 // appropriately-documented code.
219 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
220 code:GRPCErrorCodeInternal
221 userInfo:nil]];
222 [weakSelf cancelCall];
223 return;
224 }
Jorge Canizales4c6f7782015-07-17 23:13:36 -0700225 [weakWriteable enqueueValue:data completionHandler:^{
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800226 [weakSelf startNextRead];
227 }];
228 }];
229 });
230}
231
232#pragma mark Send headers
233
murgatroid9969927d62015-04-24 13:32:48 -0700234// TODO(jcanizales): Rename to commitHeaders.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800235- (void)sendHeaders:(NSDictionary *)metadata {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700236 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700237 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
238 initWithMetadata:metadata ?: @{} handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800239}
240
241#pragma mark GRXWriteable implementation
242
243// Only called from the call queue. The error handler will be called from the
244// network queue if the write didn't succeed.
245- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
246
247 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700248 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700249 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800250 GRPCCall *strongSelf = weakSelf;
251 if (strongSelf) {
Jorge Canizales238ad782015-08-07 23:11:29 -0700252 @synchronized(_requestWriter) {
253 strongSelf->_requestWriter.state = GRXWriterStateStarted;
254 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800255 }
256 };
Jorge Canizales238ad782015-08-07 23:11:29 -0700257 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message
258 handler:resumingHandler]]
259 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800260}
261
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700262- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800263 // TODO(jcanizales): Throw/assert if value isn't NSData.
264
265 // Pause the input and only resume it when the C layer notifies us that writes
266 // can proceed.
Jorge Canizales238ad782015-08-07 23:11:29 -0700267 @synchronized(_requestWriter) {
268 _requestWriter.state = GRXWriterStatePaused;
269 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800270
271 __weak GRPCCall *weakSelf = self;
272 dispatch_async(_callQueue, ^{
273 [weakSelf writeMessage:value withErrorHandler:^{
274 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
275 code:GRPCErrorCodeInternal
276 userInfo:nil]];
277 }];
278 });
279}
280
281// Only called from the call queue. The error handler will be called from the
282// network queue if the requests stream couldn't be closed successfully.
283- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid996cc46802015-04-28 09:35:48 -0700284 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
285 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800286}
287
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700288- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales238ad782015-08-07 23:11:29 -0700289 @synchronized(_requestWriter) {
290 _requestWriter = nil;
291 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800292 if (errorOrNil) {
293 [self cancel];
294 } else {
295 __weak GRPCCall *weakSelf = self;
296 dispatch_async(_callQueue, ^{
297 [weakSelf finishRequestWithErrorHandler:^{
298 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
299 code:GRPCErrorCodeInternal
300 userInfo:nil]];
301 }];
302 });
303 }
304}
305
306#pragma mark Invoke
307
308// Both handlers will eventually be called, from the network queue. Writes can start immediately
309// after this.
310// The first one (metadataHandler), when the response headers are received.
311// The second one (completionHandler), whenever the RPC finishes for any reason.
murgatroid9954e93d42015-04-27 09:29:49 -0700312- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700313 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700314 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700315 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
316 initWithHandler:metadataHandler]]];
317 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
318 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800319}
320
321- (void)invokeCall {
322 __weak GRPCCall *weakSelf = self;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700323 [self invokeCallWithMetadataHandler:^(NSDictionary *headers) {
324 // Response headers received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800325 GRPCCall *strongSelf = weakSelf;
326 if (strongSelf) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700327 [strongSelf->_responseMetadata addEntriesFromDictionary:headers];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800328 [strongSelf startNextRead];
329 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700330 } completionHandler:^(NSError *error, NSDictionary *trailers) {
331 GRPCCall *strongSelf = weakSelf;
332 if (strongSelf) {
333 [strongSelf->_responseMetadata addEntriesFromDictionary:trailers];
334
Jorge Canizalesc58a1102015-06-15 19:03:41 -0700335 if (error) {
336 NSMutableDictionary *userInfo =
337 [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
338 userInfo[kGRPCStatusMetadataKey] = strongSelf->_responseMetadata;
339 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
340 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700341 [strongSelf finishWithError:error];
342 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800343 }];
344 // Now that the RPC has been initiated, request writes can start.
Jorge Canizales238ad782015-08-07 23:11:29 -0700345 @synchronized(_requestWriter) {
346 [_requestWriter startWithWriteable:self];
347 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800348}
349
350#pragma mark GRXWriter implementation
351
352- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700353 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
354 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
355 // before being autoreleased).
356 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
357 // that the life of the instance is determined by this retain cycle.
358 _self = self;
359
360 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800361 [self sendHeaders:_requestMetadata];
362 [self invokeCall];
363}
364
365- (void)setState:(GRXWriterState)newState {
366 // Manual transitions are only allowed from the started or paused states.
367 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
368 return;
369 }
370
371 switch (newState) {
372 case GRXWriterStateFinished:
373 _state = newState;
374 // Per GRXWriter's contract, setting the state to Finished manually
375 // means one doesn't wish the writeable to be messaged anymore.
376 [_responseWriteable cancelSilently];
377 _responseWriteable = nil;
378 return;
379 case GRXWriterStatePaused:
380 _state = newState;
381 return;
382 case GRXWriterStateStarted:
383 if (_state == GRXWriterStatePaused) {
384 _state = newState;
385 [self startNextRead];
386 }
387 return;
388 case GRXWriterStateNotStarted:
389 return;
390 }
391}
392@end