blob: f9d13fea578b7a5124e0ec27d280b9bfc088c182 [file] [log] [blame]
Jorge Canizales9409ad82015-02-18 16:19:56 -08001/*
2 *
Craig Tiller6169d5f2016-03-31 07:46:18 -07003 * 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>
Muxi Yan61274ca2016-10-28 12:09:59 -070038#import <RxLibrary/GRXConcurrentWriteable.h>
Muxi Yana40ccd82016-11-05 21:39:44 -070039#import <RxLibrary/GRXImmediateSingleWriter.h>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080040
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070041#import "private/GRPCConnectivityMonitor.h"
42#import "private/GRPCHost.h"
Jorge Canizales2f101272015-09-02 21:55:37 -070043#import "private/GRPCRequestHeaders.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
Muxi Yan016d1082017-03-07 18:26:42 -080049// At most 6 ops can be in an op batch for a client: SEND_INITIAL_METADATA,
50// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT, RECV_INITIAL_METADATA, RECV_MESSAGE,
51// and RECV_STATUS_ON_CLIENT.
52NSInteger kMaxClientBatch = 6;
53
Muxi Yan61274ca2016-10-28 12:09:59 -070054NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
55NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
Muxi Yan22f79732016-09-30 14:24:56 -070056static NSMutableDictionary *callFlags;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070057
Muxi Yan61274ca2016-10-28 12:09:59 -070058@interface GRPCCall () <GRXWriteable>
Jorge Canizales0b34c892015-08-12 20:19:20 -070059// Make them read-write.
60@property(atomic, strong) NSDictionary *responseHeaders;
61@property(atomic, strong) NSDictionary *responseTrailers;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080062@end
63
64// The following methods of a C gRPC call object aren't reentrant, and thus
65// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070066// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080067// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080068//
murgatroid99b5c076f2015-04-27 17:25:36 -070069// start_batch with a SEND_MESSAGE argument can only be called after the
70// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080071// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070072// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080073//
murgatroid99b5c076f2015-04-27 17:25:36 -070074// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
75// the OP_COMPLETE event for any previous read is received.This is easier to
76// enforce, as we're writing the received messages into the writeable:
77// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
78// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
79// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080080@implementation GRPCCall {
81 dispatch_queue_t _callQueue;
82
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070083 NSString *_host;
84 NSString *_path;
murgatroid9930b7d4e2015-04-24 10:36:43 -070085 GRPCWrappedCall *_wrappedCall;
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070086 GRPCConnectivityMonitor *_connectivityMonitor;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080087
Jorge Canizales5e0efd92015-02-17 18:23:58 -080088 // The C gRPC library has less guarantees on the ordering of events than we
89 // do. Particularly, in the face of errors, there's no ordering guarantee at
90 // all. This wrapper over our actual writeable ensures thread-safety and
91 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070092 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales238ad782015-08-07 23:11:29 -070093
Muxi Yan61274ca2016-10-28 12:09:59 -070094 // The network thread wants the requestWriter to resume (when the server is ready for more input),
95 // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
96 // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
97 // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
98 // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
99 // pause the writer immediately on writeValue:, so we need our locking to be recursive.
Jorge Canizales56047122015-07-17 12:18:08 -0700100 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700101
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700102 // To create a retain cycle when a call is started, up until it finishes. See
Muxi Yan61274ca2016-10-28 12:09:59 -0700103 // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
104 // reference to the call object if all they're interested in is the handler being executed when
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700105 // the response arrives.
106 GRPCCall *_retainSelf;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700107
murgatroid9984fa5312015-08-28 10:55:55 -0700108 GRPCRequestHeaders *_requestHeaders;
Muxi Yana40ccd82016-11-05 21:39:44 -0700109
Muxi Yanc2e53b52017-03-22 14:30:16 -0700110 // In the case that the call is a unary call (i.e. the writer to GRPCCall is of type
111 // GRXImmediateSingleWriter), GRPCCall will delay sending ops (not send them to C core
112 // immediately) and buffer them into a batch _unaryOpBatch. The batch is sent to C core when
113 // the SendClose op is added.
Muxi Yana40ccd82016-11-05 21:39:44 -0700114 BOOL _unaryCall;
Muxi Yana40ccd82016-11-05 21:39:44 -0700115 NSMutableArray *_unaryOpBatch;
Muxi Yan895f3d82017-04-05 13:12:30 -0700116
117 // The dispatch queue to be used for enqueuing responses to user. Defaulted to the main dispatch
118 // queue
119 dispatch_queue_t _responseQueue;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800120}
121
122@synthesize state = _state;
123
Muxi Yan61274ca2016-10-28 12:09:59 -0700124// TODO(jcanizales): If grpc_init is idempotent, this should be changed from load to initialize.
Jorge Canizales7603d642016-08-24 18:23:24 -0700125+ (void)load {
126 grpc_init();
Muxi Yan22f79732016-09-30 14:24:56 -0700127 callFlags = [NSMutableDictionary dictionary];
128}
129
Muxi Yan61274ca2016-10-28 12:09:59 -0700130+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
Muxi Yan6c0b9602016-10-02 14:32:06 -0700131 NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
132 switch (callSafety) {
133 case GRPCCallSafetyDefault:
Muxi Yan22f79732016-09-30 14:24:56 -0700134 callFlags[hostAndPath] = @0;
135 break;
Muxi Yan6c0b9602016-10-02 14:32:06 -0700136 case GRPCCallSafetyIdempotentRequest:
Muxi Yan22f79732016-09-30 14:24:56 -0700137 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
138 break;
Muxi Yan6c0b9602016-10-02 14:32:06 -0700139 case GRPCCallSafetyCacheableRequest:
Muxi Yan22f79732016-09-30 14:24:56 -0700140 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
141 break;
142 default:
143 break;
144 }
145}
146
Muxi Yan6c0b9602016-10-02 14:32:06 -0700147+ (uint32_t)callFlagsForHost:(NSString *)host path:(NSString *)path {
148 NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
149 return [callFlags[hostAndPath] intValue];
Jorge Canizales7603d642016-08-24 18:23:24 -0700150}
151
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800152- (instancetype)init {
Muxi Yan22f79732016-09-30 14:24:56 -0700153 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800154}
155
156// Designated initializer
157- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700158 path:(NSString *)path
Muxi Yan22f79732016-09-30 14:24:56 -0700159 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700160 if (!host || !path) {
Muxi Yan61274ca2016-10-28 12:09:59 -0700161 [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800162 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700163 if (requestWriter.state != GRXWriterStateNotStarted) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700164 [NSException raise:NSInvalidArgumentException
165 format:@"The requests writer can't be already started."];
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700166 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800167 if ((self = [super init])) {
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700168 _host = [host copy];
169 _path = [path copy];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800170
171 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700172 _callQueue = dispatch_queue_create("io.grpc.call", NULL);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800173
174 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700175
murgatroid9984fa5312015-08-28 10:55:55 -0700176 _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
Muxi Yana40ccd82016-11-05 21:39:44 -0700177
178 if ([requestWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800179 _unaryCall = YES;
Muxi Yan016d1082017-03-07 18:26:42 -0800180 _unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
Muxi Yana40ccd82016-11-05 21:39:44 -0700181 }
Muxi Yan895f3d82017-04-05 13:12:30 -0700182
183 _responseQueue = dispatch_get_main_queue();
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800184 }
185 return self;
186}
187
Muxi Yan895f3d82017-04-05 13:12:30 -0700188- (void)setResponseDispatchQueue:(dispatch_queue_t)queue {
189 if (_state != GRXWriterStateNotStarted) {
190 return;
191 }
192 _responseQueue = queue;
193}
194
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800195#pragma mark Finish
196
197- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700198 @synchronized(self) {
Muxi Yana40ccd82016-11-05 21:39:44 -0700199 if (_state == GRXWriterStateFinished) {
200 return;
201 }
Jorge Canizales0803bb02016-04-30 10:40:18 -0700202 _state = GRXWriterStateFinished;
203 }
204
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700205 // If the call isn't retained anywhere else, it can be deallocated now.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700206 _retainSelf = nil;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700207
208 // If there were still request messages coming, stop them.
Jorge Canizales238ad782015-08-07 23:11:29 -0700209 @synchronized(_requestWriter) {
210 _requestWriter.state = GRXWriterStateFinished;
Jorge Canizales238ad782015-08-07 23:11:29 -0700211 }
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700212
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800213 if (errorOrNil) {
214 [_responseWriteable cancelWithError:errorOrNil];
215 } else {
216 [_responseWriteable enqueueSuccessfulCompletion];
217 }
218}
219
220- (void)cancelCall {
221 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700222 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800223}
224
225- (void)cancel {
226 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
227 code:GRPCErrorCodeCancelled
Muxi Yan61274ca2016-10-28 12:09:59 -0700228 userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800229 [self cancelCall];
230}
231
232- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700233 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800234 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700235 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800236 });
237}
238
239#pragma mark Read messages
240
241// Only called from the call queue.
242// The handler will be called from the network queue.
Muxi Yan61274ca2016-10-28 12:09:59 -0700243- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700244 // TODO(jcanizales): Add error handlers for async failures
Muxi Yan61274ca2016-10-28 12:09:59 -0700245 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800246}
247
248// Called initially from the network queue once response headers are received,
Muxi Yan61274ca2016-10-28 12:09:59 -0700249// then "recursively" from the responseWriteable queue after each response from the
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800250// server has been written.
Muxi Yan61274ca2016-10-28 12:09:59 -0700251// If the call is currently paused, this is a noop. Restarting the call will invoke this
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800252// method.
253// TODO(jcanizales): Rename to readResponseIfNotPaused.
254- (void)startNextRead {
255 if (self.state == GRXWriterStatePaused) {
256 return;
257 }
258 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700259 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700260
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800261 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700262 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
263 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700264 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800265 return;
266 }
murgatroid996cc46802015-04-28 09:35:48 -0700267 NSData *data = [NSData grpc_dataWithByteBuffer:message];
268 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800269 if (!data) {
270 // The app doesn't have enough memory to hold the server response. We
271 // don't want to throw, because the app shouldn't crash for a behavior
272 // that's on the hands of any server to have. Instead we finish and ask
273 // the server to cancel.
Muxi Yan61274ca2016-10-28 12:09:59 -0700274 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
275 code:GRPCErrorCodeResourceExhausted
276 userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800277 [weakSelf cancelCall];
278 return;
279 }
Muxi Yan61274ca2016-10-28 12:09:59 -0700280 [weakWriteable enqueueValue:data completionHandler:^{
281 [weakSelf startNextRead];
282 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800283 }];
284 });
285}
286
287#pragma mark Send headers
288
Jorge Canizalesf4f150f2015-11-01 22:31:12 -0800289- (void)sendHeaders:(NSDictionary *)headers {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700290 // TODO(jcanizales): Add error handlers for async failures
Muxi Yanbf803b92017-02-08 11:31:26 -0800291 GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc] initWithMetadata:headers
292 flags:[GRPCCall callFlagsForHost:_host path:_path]
Muxi Yand5bac0d2017-03-07 18:44:19 -0800293 handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA
Muxi Yana40ccd82016-11-05 21:39:44 -0700294 if (!_unaryCall) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800295 [_wrappedCall startBatchWithOperations:@[op]];
Muxi Yana40ccd82016-11-05 21:39:44 -0700296 } else {
Muxi Yanbf803b92017-02-08 11:31:26 -0800297 [_unaryOpBatch addObject:op];
Muxi Yana40ccd82016-11-05 21:39:44 -0700298 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800299}
300
301#pragma mark GRXWriteable implementation
302
303// Only called from the call queue. The error handler will be called from the
304// network queue if the write didn't succeed.
Muxi Yan35653012017-03-07 18:54:24 -0800305// If the call is a unary call, parameter \a errorHandler will be ignored and
306// the error handler of GRPCOpSendClose will be executed in case of error.
Muxi Yan61274ca2016-10-28 12:09:59 -0700307- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
308
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800309 __weak GRPCCall *weakSelf = self;
Muxi Yan61274ca2016-10-28 12:09:59 -0700310 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700311 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800312 GRPCCall *strongSelf = weakSelf;
313 if (strongSelf) {
Jorge Canizales578ab162015-08-08 17:11:43 -0700314 @synchronized(strongSelf->_requestWriter) {
Jorge Canizales238ad782015-08-07 23:11:29 -0700315 strongSelf->_requestWriter.state = GRXWriterStateStarted;
316 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800317 }
318 };
Muxi Yanbf803b92017-02-08 11:31:26 -0800319
320 GRPCOpSendMessage *op = [[GRPCOpSendMessage alloc] initWithMessage:message
321 handler:resumingHandler];
Muxi Yana40ccd82016-11-05 21:39:44 -0700322 if (!_unaryCall) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800323 [_wrappedCall startBatchWithOperations:@[op]
Muxi Yana40ccd82016-11-05 21:39:44 -0700324 errorHandler:errorHandler];
325 } else {
Muxi Yan44e18372017-03-10 14:52:51 -0800326 // Ignored errorHandler since it is the same as the one for GRPCOpSendClose.
327 // TODO (mxyan): unify the error handlers of all Ops into a single closure.
Muxi Yanbf803b92017-02-08 11:31:26 -0800328 [_unaryOpBatch addObject:op];
Muxi Yana40ccd82016-11-05 21:39:44 -0700329 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800330}
331
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700332- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800333 // TODO(jcanizales): Throw/assert if value isn't NSData.
334
335 // Pause the input and only resume it when the C layer notifies us that writes
336 // can proceed.
Jorge Canizales238ad782015-08-07 23:11:29 -0700337 @synchronized(_requestWriter) {
338 _requestWriter.state = GRXWriterStatePaused;
339 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800340
341 __weak GRPCCall *weakSelf = self;
342 dispatch_async(_callQueue, ^{
Muxi Yan61274ca2016-10-28 12:09:59 -0700343 [weakSelf writeMessage:value withErrorHandler:^{
344 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800345 code:GRPCErrorCodeInternal
346 userInfo:nil]];
Muxi Yan61274ca2016-10-28 12:09:59 -0700347 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800348 });
349}
350
351// Only called from the call queue. The error handler will be called from the
352// network queue if the requests stream couldn't be closed successfully.
353- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
Muxi Yand5bac0d2017-03-07 18:44:19 -0800354 if (!_unaryCall) {
Muxi Yana40ccd82016-11-05 21:39:44 -0700355 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
356 errorHandler:errorHandler];
357 } else {
358 [_unaryOpBatch addObject:[[GRPCOpSendClose alloc] init]];
359 [_wrappedCall startBatchWithOperations:_unaryOpBatch
360 errorHandler:errorHandler];
361 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800362}
363
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700364- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800365 if (errorOrNil) {
366 [self cancel];
367 } else {
368 __weak GRPCCall *weakSelf = self;
369 dispatch_async(_callQueue, ^{
370 [weakSelf finishRequestWithErrorHandler:^{
371 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
372 code:GRPCErrorCodeInternal
373 userInfo:nil]];
374 }];
375 });
376 }
377}
378
379#pragma mark Invoke
380
Muxi Yan61274ca2016-10-28 12:09:59 -0700381// Both handlers will eventually be called, from the network queue. Writes can start immediately
382// after this.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700383// The first one (headersHandler), when the response headers are received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800384// The second one (completionHandler), whenever the RPC finishes for any reason.
Muxi Yan61274ca2016-10-28 12:09:59 -0700385- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler
386 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700387 // TODO(jcanizales): Add error handlers for async failures
Muxi Yan61274ca2016-10-28 12:09:59 -0700388 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
389 initWithHandler:headersHandler]]];
390 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
391 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800392}
393
394- (void)invokeCall {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700395 [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700396 // Response headers received.
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100397 self.responseHeaders = headers;
398 [self startNextRead];
Muxi Yan61274ca2016-10-28 12:09:59 -0700399 } completionHandler:^(NSError *error, NSDictionary *trailers) {
400 self.responseTrailers = trailers;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700401
Muxi Yan61274ca2016-10-28 12:09:59 -0700402 if (error) {
403 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
404 if (error.userInfo) {
405 [userInfo addEntriesFromDictionary:error.userInfo];
406 }
407 userInfo[kGRPCTrailersKey] = self.responseTrailers;
408 // TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be
409 // called before this one, so an error might end up with trailers but no headers. We
410 // shouldn't call finishWithError until ater both blocks are called. It is also when this is
411 // done that we can provide a merged view of response headers and trailers in a thread-safe
412 // way.
413 if (self.responseHeaders) {
414 userInfo[kGRPCHeadersKey] = self.responseHeaders;
415 }
416 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
417 }
418 [self finishWithError:error];
419 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800420 // Now that the RPC has been initiated, request writes can start.
Jorge Canizales238ad782015-08-07 23:11:29 -0700421 @synchronized(_requestWriter) {
422 [_requestWriter startWithWriteable:self];
423 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800424}
425
426#pragma mark GRXWriter implementation
427
428- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700429 @synchronized(self) {
430 _state = GRXWriterStateStarted;
431 }
432
Muxi Yan61274ca2016-10-28 12:09:59 -0700433 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
434 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
435 // before being autoreleased).
436 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
437 // that the life of the instance is determined by this retain cycle.
Muxi Yane1443b12016-10-20 11:53:13 -0700438 _retainSelf = self;
439
Muxi Yan895f3d82017-04-05 13:12:30 -0700440 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable
441 dispatchQueue:_responseQueue];
Muxi Yane1443b12016-10-20 11:53:13 -0700442
443 _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path];
444 NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
445
446 [self sendHeaders:_requestHeaders];
447 [self invokeCall];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700448
449 // TODO(jcanizales): Extract this logic somewhere common.
Muxi Yan61274ca2016-10-28 12:09:59 -0700450 NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700451 if (!host) {
452 // TODO(jcanizales): Check this on init.
Muxi Yan61274ca2016-10-28 12:09:59 -0700453 [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700454 }
455 __weak typeof(self) weakSelf = self;
456 _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
457 void (^handler)() = ^{
458 typeof(self) strongSelf = weakSelf;
459 if (strongSelf) {
Muxi Yan61274ca2016-10-28 12:09:59 -0700460 [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
461 code:GRPCErrorCodeUnavailable
462 userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700463 }
464 };
465 [_connectivityMonitor handleLossWithHandler:handler
Muxi Yan2a25f332016-10-28 13:27:04 -0700466 wifiStatusChangeHandler:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800467}
468
469- (void)setState:(GRXWriterState)newState {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700470 @synchronized(self) {
471 // Manual transitions are only allowed from the started or paused states.
Muxi Yan61274ca2016-10-28 12:09:59 -0700472 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700473 return;
474 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800475
Jorge Canizales0803bb02016-04-30 10:40:18 -0700476 switch (newState) {
477 case GRXWriterStateFinished:
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800478 _state = newState;
Jorge Canizales0803bb02016-04-30 10:40:18 -0700479 // Per GRXWriter's contract, setting the state to Finished manually
480 // means one doesn't wish the writeable to be messaged anymore.
481 [_responseWriteable cancelSilently];
482 _responseWriteable = nil;
483 return;
484 case GRXWriterStatePaused:
485 _state = newState;
486 return;
487 case GRXWriterStateStarted:
488 if (_state == GRXWriterStatePaused) {
489 _state = newState;
490 [self startNextRead];
491 }
492 return;
493 case GRXWriterStateNotStarted:
494 return;
495 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800496 }
497}
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700498
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800499@end