blob: 6ba401def44b81747e2964d99c9ce0c07fbf1bbe [file] [log] [blame]
Jorge Canizales9409ad82015-02-18 16:19:56 -08001/*
2 *
Jan Tattermusch7897ae92017-06-07 22:57:36 +02003 * Copyright 2015 gRPC authors.
Jorge Canizales9409ad82015-02-18 16:19:56 -08004 *
Jan Tattermusch7897ae92017-06-07 22:57:36 +02005 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
Jorge Canizales9409ad82015-02-18 16:19:56 -08008 *
Jan Tattermusch7897ae92017-06-07 22:57:36 +02009 * http://www.apache.org/licenses/LICENSE-2.0
Jorge Canizales9409ad82015-02-18 16:19:56 -080010 *
Jan Tattermusch7897ae92017-06-07 22:57:36 +020011 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
Jorge Canizales9409ad82015-02-18 16:19:56 -080016 *
17 */
18
Jorge Canizales5e0efd92015-02-17 18:23:58 -080019#import "GRPCCall.h"
20
Jorge Canizalesc2d7ecb2015-02-27 01:22:41 -080021#include <grpc/grpc.h>
Jorge Canizales59bb9d72015-06-22 19:04:15 -070022#include <grpc/support/time.h>
Muxi Yan61274ca2016-10-28 12:09:59 -070023#import <RxLibrary/GRXConcurrentWriteable.h>
Muxi Yana40ccd82016-11-05 21:39:44 -070024#import <RxLibrary/GRXImmediateSingleWriter.h>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080025
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070026#import "private/GRPCConnectivityMonitor.h"
27#import "private/GRPCHost.h"
Jorge Canizales2f101272015-09-02 21:55:37 -070028#import "private/GRPCRequestHeaders.h"
murgatroid9969927d62015-04-24 13:32:48 -070029#import "private/GRPCWrappedCall.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080030#import "private/NSData+GRPC.h"
31#import "private/NSDictionary+GRPC.h"
32#import "private/NSError+GRPC.h"
33
Muxi Yan016d1082017-03-07 18:26:42 -080034// At most 6 ops can be in an op batch for a client: SEND_INITIAL_METADATA,
35// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT, RECV_INITIAL_METADATA, RECV_MESSAGE,
36// and RECV_STATUS_ON_CLIENT.
37NSInteger kMaxClientBatch = 6;
38
Muxi Yan61274ca2016-10-28 12:09:59 -070039NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
40NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
Muxi Yan22f79732016-09-30 14:24:56 -070041static NSMutableDictionary *callFlags;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070042
Muxi Yan61274ca2016-10-28 12:09:59 -070043@interface GRPCCall () <GRXWriteable>
Jorge Canizales0b34c892015-08-12 20:19:20 -070044// Make them read-write.
45@property(atomic, strong) NSDictionary *responseHeaders;
46@property(atomic, strong) NSDictionary *responseTrailers;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080047@end
48
49// The following methods of a C gRPC call object aren't reentrant, and thus
50// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070051// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080052// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080053//
murgatroid99b5c076f2015-04-27 17:25:36 -070054// start_batch with a SEND_MESSAGE argument can only be called after the
55// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080056// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070057// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080058//
murgatroid99b5c076f2015-04-27 17:25:36 -070059// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
60// the OP_COMPLETE event for any previous read is received.This is easier to
61// enforce, as we're writing the received messages into the writeable:
62// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
63// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
64// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080065@implementation GRPCCall {
66 dispatch_queue_t _callQueue;
67
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070068 NSString *_host;
69 NSString *_path;
murgatroid9930b7d4e2015-04-24 10:36:43 -070070 GRPCWrappedCall *_wrappedCall;
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070071 GRPCConnectivityMonitor *_connectivityMonitor;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080072
Jorge Canizales5e0efd92015-02-17 18:23:58 -080073 // The C gRPC library has less guarantees on the ordering of events than we
74 // do. Particularly, in the face of errors, there's no ordering guarantee at
75 // all. This wrapper over our actual writeable ensures thread-safety and
76 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070077 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales238ad782015-08-07 23:11:29 -070078
Muxi Yan61274ca2016-10-28 12:09:59 -070079 // The network thread wants the requestWriter to resume (when the server is ready for more input),
80 // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
81 // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
82 // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
83 // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
84 // pause the writer immediately on writeValue:, so we need our locking to be recursive.
Jorge Canizales56047122015-07-17 12:18:08 -070085 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070086
Jorge Canizales6531b2b2015-07-18 00:19:14 -070087 // To create a retain cycle when a call is started, up until it finishes. See
Muxi Yan61274ca2016-10-28 12:09:59 -070088 // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
89 // reference to the call object if all they're interested in is the handler being executed when
Jorge Canizaleseb87b462015-08-08 16:16:43 -070090 // the response arrives.
91 GRPCCall *_retainSelf;
Jorge Canizales6531b2b2015-07-18 00:19:14 -070092
murgatroid9984fa5312015-08-28 10:55:55 -070093 GRPCRequestHeaders *_requestHeaders;
Muxi Yana40ccd82016-11-05 21:39:44 -070094
Muxi Yanc2e53b52017-03-22 14:30:16 -070095 // In the case that the call is a unary call (i.e. the writer to GRPCCall is of type
96 // GRXImmediateSingleWriter), GRPCCall will delay sending ops (not send them to C core
97 // immediately) and buffer them into a batch _unaryOpBatch. The batch is sent to C core when
98 // the SendClose op is added.
Muxi Yana40ccd82016-11-05 21:39:44 -070099 BOOL _unaryCall;
Muxi Yana40ccd82016-11-05 21:39:44 -0700100 NSMutableArray *_unaryOpBatch;
Muxi Yan895f3d82017-04-05 13:12:30 -0700101
102 // The dispatch queue to be used for enqueuing responses to user. Defaulted to the main dispatch
103 // queue
104 dispatch_queue_t _responseQueue;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800105}
106
107@synthesize state = _state;
108
Muxi Yan61274ca2016-10-28 12:09:59 -0700109// TODO(jcanizales): If grpc_init is idempotent, this should be changed from load to initialize.
Jorge Canizales7603d642016-08-24 18:23:24 -0700110+ (void)load {
111 grpc_init();
Muxi Yan22f79732016-09-30 14:24:56 -0700112 callFlags = [NSMutableDictionary dictionary];
113}
114
Muxi Yan61274ca2016-10-28 12:09:59 -0700115+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
Muxi Yan6c0b9602016-10-02 14:32:06 -0700116 NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
117 switch (callSafety) {
118 case GRPCCallSafetyDefault:
Muxi Yan22f79732016-09-30 14:24:56 -0700119 callFlags[hostAndPath] = @0;
120 break;
Muxi Yan6c0b9602016-10-02 14:32:06 -0700121 case GRPCCallSafetyIdempotentRequest:
Muxi Yan22f79732016-09-30 14:24:56 -0700122 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
123 break;
Muxi Yan6c0b9602016-10-02 14:32:06 -0700124 case GRPCCallSafetyCacheableRequest:
Muxi Yan22f79732016-09-30 14:24:56 -0700125 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
126 break;
127 default:
128 break;
129 }
130}
131
Muxi Yan6c0b9602016-10-02 14:32:06 -0700132+ (uint32_t)callFlagsForHost:(NSString *)host path:(NSString *)path {
133 NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
134 return [callFlags[hostAndPath] intValue];
Jorge Canizales7603d642016-08-24 18:23:24 -0700135}
136
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800137- (instancetype)init {
Muxi Yan22f79732016-09-30 14:24:56 -0700138 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800139}
140
141// Designated initializer
142- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700143 path:(NSString *)path
Muxi Yan22f79732016-09-30 14:24:56 -0700144 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700145 if (!host || !path) {
Muxi Yan61274ca2016-10-28 12:09:59 -0700146 [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800147 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700148 if (requestWriter.state != GRXWriterStateNotStarted) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700149 [NSException raise:NSInvalidArgumentException
150 format:@"The requests writer can't be already started."];
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700151 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800152 if ((self = [super init])) {
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700153 _host = [host copy];
154 _path = [path copy];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800155
156 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700157 _callQueue = dispatch_queue_create("io.grpc.call", NULL);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800158
159 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700160
murgatroid9984fa5312015-08-28 10:55:55 -0700161 _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
Muxi Yana40ccd82016-11-05 21:39:44 -0700162
163 if ([requestWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800164 _unaryCall = YES;
Muxi Yan016d1082017-03-07 18:26:42 -0800165 _unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
Muxi Yana40ccd82016-11-05 21:39:44 -0700166 }
Muxi Yan895f3d82017-04-05 13:12:30 -0700167
168 _responseQueue = dispatch_get_main_queue();
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800169 }
170 return self;
171}
172
Muxi Yan895f3d82017-04-05 13:12:30 -0700173- (void)setResponseDispatchQueue:(dispatch_queue_t)queue {
174 if (_state != GRXWriterStateNotStarted) {
175 return;
176 }
177 _responseQueue = queue;
178}
179
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800180#pragma mark Finish
181
182- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700183 @synchronized(self) {
Muxi Yana40ccd82016-11-05 21:39:44 -0700184 if (_state == GRXWriterStateFinished) {
185 return;
186 }
Jorge Canizales0803bb02016-04-30 10:40:18 -0700187 _state = GRXWriterStateFinished;
188 }
189
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700190 // If the call isn't retained anywhere else, it can be deallocated now.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700191 _retainSelf = nil;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700192
193 // If there were still request messages coming, stop them.
Jorge Canizales238ad782015-08-07 23:11:29 -0700194 @synchronized(_requestWriter) {
195 _requestWriter.state = GRXWriterStateFinished;
Jorge Canizales238ad782015-08-07 23:11:29 -0700196 }
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700197
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800198 if (errorOrNil) {
199 [_responseWriteable cancelWithError:errorOrNil];
200 } else {
201 [_responseWriteable enqueueSuccessfulCompletion];
202 }
203}
204
205- (void)cancelCall {
206 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700207 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800208}
209
210- (void)cancel {
211 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
212 code:GRPCErrorCodeCancelled
Muxi Yan61274ca2016-10-28 12:09:59 -0700213 userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800214 [self cancelCall];
215}
216
217- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700218 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800219 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700220 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800221 });
222}
223
224#pragma mark Read messages
225
226// Only called from the call queue.
227// The handler will be called from the network queue.
Muxi Yan61274ca2016-10-28 12:09:59 -0700228- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700229 // TODO(jcanizales): Add error handlers for async failures
Muxi Yan61274ca2016-10-28 12:09:59 -0700230 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800231}
232
233// Called initially from the network queue once response headers are received,
Muxi Yan61274ca2016-10-28 12:09:59 -0700234// then "recursively" from the responseWriteable queue after each response from the
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800235// server has been written.
Muxi Yan61274ca2016-10-28 12:09:59 -0700236// If the call is currently paused, this is a noop. Restarting the call will invoke this
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800237// method.
238// TODO(jcanizales): Rename to readResponseIfNotPaused.
239- (void)startNextRead {
240 if (self.state == GRXWriterStatePaused) {
241 return;
242 }
243 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700244 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700245
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800246 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700247 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
248 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700249 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800250 return;
251 }
murgatroid996cc46802015-04-28 09:35:48 -0700252 NSData *data = [NSData grpc_dataWithByteBuffer:message];
253 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800254 if (!data) {
255 // The app doesn't have enough memory to hold the server response. We
256 // don't want to throw, because the app shouldn't crash for a behavior
257 // that's on the hands of any server to have. Instead we finish and ask
258 // the server to cancel.
Muxi Yan61274ca2016-10-28 12:09:59 -0700259 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
260 code:GRPCErrorCodeResourceExhausted
261 userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800262 [weakSelf cancelCall];
263 return;
264 }
Muxi Yan61274ca2016-10-28 12:09:59 -0700265 [weakWriteable enqueueValue:data completionHandler:^{
266 [weakSelf startNextRead];
267 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800268 }];
269 });
270}
271
272#pragma mark Send headers
273
Jorge Canizalesf4f150f2015-11-01 22:31:12 -0800274- (void)sendHeaders:(NSDictionary *)headers {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700275 // TODO(jcanizales): Add error handlers for async failures
Muxi Yanbf803b92017-02-08 11:31:26 -0800276 GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc] initWithMetadata:headers
277 flags:[GRPCCall callFlagsForHost:_host path:_path]
Muxi Yand5bac0d2017-03-07 18:44:19 -0800278 handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA
Muxi Yana40ccd82016-11-05 21:39:44 -0700279 if (!_unaryCall) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800280 [_wrappedCall startBatchWithOperations:@[op]];
Muxi Yana40ccd82016-11-05 21:39:44 -0700281 } else {
Muxi Yanbf803b92017-02-08 11:31:26 -0800282 [_unaryOpBatch addObject:op];
Muxi Yana40ccd82016-11-05 21:39:44 -0700283 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800284}
285
286#pragma mark GRXWriteable implementation
287
288// Only called from the call queue. The error handler will be called from the
289// network queue if the write didn't succeed.
Muxi Yan35653012017-03-07 18:54:24 -0800290// If the call is a unary call, parameter \a errorHandler will be ignored and
291// the error handler of GRPCOpSendClose will be executed in case of error.
Muxi Yan61274ca2016-10-28 12:09:59 -0700292- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
293
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800294 __weak GRPCCall *weakSelf = self;
Muxi Yan61274ca2016-10-28 12:09:59 -0700295 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700296 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800297 GRPCCall *strongSelf = weakSelf;
298 if (strongSelf) {
Jorge Canizales578ab162015-08-08 17:11:43 -0700299 @synchronized(strongSelf->_requestWriter) {
Jorge Canizales238ad782015-08-07 23:11:29 -0700300 strongSelf->_requestWriter.state = GRXWriterStateStarted;
301 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800302 }
303 };
Muxi Yanbf803b92017-02-08 11:31:26 -0800304
305 GRPCOpSendMessage *op = [[GRPCOpSendMessage alloc] initWithMessage:message
306 handler:resumingHandler];
Muxi Yana40ccd82016-11-05 21:39:44 -0700307 if (!_unaryCall) {
Muxi Yanbf803b92017-02-08 11:31:26 -0800308 [_wrappedCall startBatchWithOperations:@[op]
Muxi Yana40ccd82016-11-05 21:39:44 -0700309 errorHandler:errorHandler];
310 } else {
Muxi Yan44e18372017-03-10 14:52:51 -0800311 // Ignored errorHandler since it is the same as the one for GRPCOpSendClose.
312 // TODO (mxyan): unify the error handlers of all Ops into a single closure.
Muxi Yanbf803b92017-02-08 11:31:26 -0800313 [_unaryOpBatch addObject:op];
Muxi Yana40ccd82016-11-05 21:39:44 -0700314 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800315}
316
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700317- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800318 // TODO(jcanizales): Throw/assert if value isn't NSData.
319
320 // Pause the input and only resume it when the C layer notifies us that writes
321 // can proceed.
Jorge Canizales238ad782015-08-07 23:11:29 -0700322 @synchronized(_requestWriter) {
323 _requestWriter.state = GRXWriterStatePaused;
324 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800325
326 __weak GRPCCall *weakSelf = self;
327 dispatch_async(_callQueue, ^{
Muxi Yan61274ca2016-10-28 12:09:59 -0700328 [weakSelf writeMessage:value withErrorHandler:^{
329 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800330 code:GRPCErrorCodeInternal
331 userInfo:nil]];
Muxi Yan61274ca2016-10-28 12:09:59 -0700332 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800333 });
334}
335
336// Only called from the call queue. The error handler will be called from the
337// network queue if the requests stream couldn't be closed successfully.
338- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
Muxi Yand5bac0d2017-03-07 18:44:19 -0800339 if (!_unaryCall) {
Muxi Yana40ccd82016-11-05 21:39:44 -0700340 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
341 errorHandler:errorHandler];
342 } else {
343 [_unaryOpBatch addObject:[[GRPCOpSendClose alloc] init]];
344 [_wrappedCall startBatchWithOperations:_unaryOpBatch
345 errorHandler:errorHandler];
346 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800347}
348
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700349- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800350 if (errorOrNil) {
351 [self cancel];
352 } else {
353 __weak GRPCCall *weakSelf = self;
354 dispatch_async(_callQueue, ^{
355 [weakSelf finishRequestWithErrorHandler:^{
356 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
357 code:GRPCErrorCodeInternal
358 userInfo:nil]];
359 }];
360 });
361 }
362}
363
364#pragma mark Invoke
365
Muxi Yan61274ca2016-10-28 12:09:59 -0700366// Both handlers will eventually be called, from the network queue. Writes can start immediately
367// after this.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700368// The first one (headersHandler), when the response headers are received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800369// The second one (completionHandler), whenever the RPC finishes for any reason.
Muxi Yan61274ca2016-10-28 12:09:59 -0700370- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler
371 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700372 // TODO(jcanizales): Add error handlers for async failures
Muxi Yan61274ca2016-10-28 12:09:59 -0700373 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
374 initWithHandler:headersHandler]]];
375 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
376 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800377}
378
379- (void)invokeCall {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700380 [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700381 // Response headers received.
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100382 self.responseHeaders = headers;
383 [self startNextRead];
Muxi Yan61274ca2016-10-28 12:09:59 -0700384 } completionHandler:^(NSError *error, NSDictionary *trailers) {
385 self.responseTrailers = trailers;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700386
Muxi Yan61274ca2016-10-28 12:09:59 -0700387 if (error) {
388 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
389 if (error.userInfo) {
390 [userInfo addEntriesFromDictionary:error.userInfo];
391 }
392 userInfo[kGRPCTrailersKey] = self.responseTrailers;
393 // TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be
394 // called before this one, so an error might end up with trailers but no headers. We
395 // shouldn't call finishWithError until ater both blocks are called. It is also when this is
396 // done that we can provide a merged view of response headers and trailers in a thread-safe
397 // way.
398 if (self.responseHeaders) {
399 userInfo[kGRPCHeadersKey] = self.responseHeaders;
400 }
401 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
402 }
403 [self finishWithError:error];
404 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800405 // Now that the RPC has been initiated, request writes can start.
Jorge Canizales238ad782015-08-07 23:11:29 -0700406 @synchronized(_requestWriter) {
407 [_requestWriter startWithWriteable:self];
408 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800409}
410
411#pragma mark GRXWriter implementation
412
413- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700414 @synchronized(self) {
415 _state = GRXWriterStateStarted;
416 }
417
Muxi Yan61274ca2016-10-28 12:09:59 -0700418 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
419 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
420 // before being autoreleased).
421 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
422 // that the life of the instance is determined by this retain cycle.
Muxi Yane1443b12016-10-20 11:53:13 -0700423 _retainSelf = self;
424
Muxi Yan895f3d82017-04-05 13:12:30 -0700425 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable
426 dispatchQueue:_responseQueue];
Muxi Yane1443b12016-10-20 11:53:13 -0700427
428 _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path];
429 NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
430
431 [self sendHeaders:_requestHeaders];
432 [self invokeCall];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700433
434 // TODO(jcanizales): Extract this logic somewhere common.
Muxi Yan61274ca2016-10-28 12:09:59 -0700435 NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700436 if (!host) {
437 // TODO(jcanizales): Check this on init.
Muxi Yan61274ca2016-10-28 12:09:59 -0700438 [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700439 }
440 __weak typeof(self) weakSelf = self;
441 _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
442 void (^handler)() = ^{
443 typeof(self) strongSelf = weakSelf;
444 if (strongSelf) {
Muxi Yan61274ca2016-10-28 12:09:59 -0700445 [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
446 code:GRPCErrorCodeUnavailable
447 userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]];
Muxi Yan4f6a19b2016-10-25 14:40:06 -0700448 }
449 };
450 [_connectivityMonitor handleLossWithHandler:handler
Muxi Yan2a25f332016-10-28 13:27:04 -0700451 wifiStatusChangeHandler:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800452}
453
454- (void)setState:(GRXWriterState)newState {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700455 @synchronized(self) {
456 // Manual transitions are only allowed from the started or paused states.
Muxi Yan61274ca2016-10-28 12:09:59 -0700457 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700458 return;
459 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800460
Jorge Canizales0803bb02016-04-30 10:40:18 -0700461 switch (newState) {
462 case GRXWriterStateFinished:
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800463 _state = newState;
Jorge Canizales0803bb02016-04-30 10:40:18 -0700464 // Per GRXWriter's contract, setting the state to Finished manually
465 // means one doesn't wish the writeable to be messaged anymore.
466 [_responseWriteable cancelSilently];
467 _responseWriteable = nil;
468 return;
469 case GRXWriterStatePaused:
470 _state = newState;
471 return;
472 case GRXWriterStateStarted:
473 if (_state == GRXWriterStatePaused) {
474 _state = newState;
475 [self startNextRead];
476 }
477 return;
478 case GRXWriterStateNotStarted:
479 return;
480 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800481 }
482}
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700483
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800484@end