blob: 2c0b7799452a15632e8bc28cf1f7824b118c4186 [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>
Jorge Canizales35f003b2015-07-17 21:14:36 -070038#import <RxLibrary/GRXConcurrentWriteable.h>
Jorge Canizales5e0efd92015-02-17 18:23:58 -080039
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070040#import "private/GRPCConnectivityMonitor.h"
41#import "private/GRPCHost.h"
Jorge Canizales2f101272015-09-02 21:55:37 -070042#import "private/GRPCRequestHeaders.h"
murgatroid9969927d62015-04-24 13:32:48 -070043#import "private/GRPCWrappedCall.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080044#import "private/NSData+GRPC.h"
45#import "private/NSDictionary+GRPC.h"
46#import "private/NSError+GRPC.h"
47
Jorge Canizales1ab2a712015-08-12 20:32:11 -070048NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
49NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
Muxi Yan22f79732016-09-30 14:24:56 -070050static NSMutableDictionary *callFlags;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070051
Jorge Canizales5e0efd92015-02-17 18:23:58 -080052@interface GRPCCall () <GRXWriteable>
Jorge Canizales0b34c892015-08-12 20:19:20 -070053// Make them read-write.
54@property(atomic, strong) NSDictionary *responseHeaders;
55@property(atomic, strong) NSDictionary *responseTrailers;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080056@end
57
58// The following methods of a C gRPC call object aren't reentrant, and thus
59// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070060// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080061// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080062//
murgatroid99b5c076f2015-04-27 17:25:36 -070063// start_batch with a SEND_MESSAGE argument can only be called after the
64// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080065// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070066// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080067//
murgatroid99b5c076f2015-04-27 17:25:36 -070068// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
69// the OP_COMPLETE event for any previous read is received.This is easier to
70// enforce, as we're writing the received messages into the writeable:
71// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
72// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
73// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080074@implementation GRPCCall {
75 dispatch_queue_t _callQueue;
76
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070077 NSString *_host;
78 NSString *_path;
murgatroid9930b7d4e2015-04-24 10:36:43 -070079 GRPCWrappedCall *_wrappedCall;
Jorge Canizalesf1d084a2015-10-30 11:14:09 -070080 GRPCConnectivityMonitor *_connectivityMonitor;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080081
Jorge Canizales5e0efd92015-02-17 18:23:58 -080082 // The C gRPC library has less guarantees on the ordering of events than we
83 // do. Particularly, in the face of errors, there's no ordering guarantee at
84 // all. This wrapper over our actual writeable ensures thread-safety and
85 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070086 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales238ad782015-08-07 23:11:29 -070087
88 // The network thread wants the requestWriter to resume (when the server is ready for more input),
89 // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
90 // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
91 // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
92 // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
93 // pause the writer immediately on writeValue:, so we need our locking to be recursive.
Jorge Canizales56047122015-07-17 12:18:08 -070094 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070095
Jorge Canizales6531b2b2015-07-18 00:19:14 -070096 // To create a retain cycle when a call is started, up until it finishes. See
Jorge Canizaleseb87b462015-08-08 16:16:43 -070097 // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
98 // reference to the call object if all they're interested in is the handler being executed when
99 // the response arrives.
100 GRPCCall *_retainSelf;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700101
murgatroid9984fa5312015-08-28 10:55:55 -0700102 GRPCRequestHeaders *_requestHeaders;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800103}
104
105@synthesize state = _state;
106
Jorge Canizales7603d642016-08-24 18:23:24 -0700107// TODO(jcanizales): If grpc_init is idempotent, this should be changed from load to initialize.
108+ (void)load {
109 grpc_init();
Muxi Yan22f79732016-09-30 14:24:56 -0700110 callFlags = [NSMutableDictionary dictionary];
111}
112
113+ (void)setCallAttribute:(GRPCCallAttr)callAttr host:(NSString *)host path:(NSString *)path {
114 NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", host, path];
115 switch (callAttr) {
116 case GRPCCallAttrDefault:
117 callFlags[hostAndPath] = @0;
118 break;
119 case GRPCCallAttrIdempotentRequest:
120 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
121 break;
122 case GRPCCallAttrCacheableRequest:
123 callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
124 break;
125 default:
126 break;
127 }
128}
129
130+ (uint32_t)getCallFlag:(NSString *)host path:(NSString *)path {
131 NSString *hostAndPath = [NSString stringWithFormat:@"%@%@", host, path];
132 if (nil != [callFlags objectForKey:hostAndPath]) {
133 return [callFlags[hostAndPath] intValue];
134 } else {
135 return 0;
136 }
Jorge Canizales7603d642016-08-24 18:23:24 -0700137}
138
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800139- (instancetype)init {
Muxi Yan22f79732016-09-30 14:24:56 -0700140 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800141}
142
143// Designated initializer
144- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700145 path:(NSString *)path
Muxi Yan22f79732016-09-30 14:24:56 -0700146 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700147 if (!host || !path) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700148 [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800149 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700150 if (requestWriter.state != GRXWriterStateNotStarted) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700151 [NSException raise:NSInvalidArgumentException
152 format:@"The requests writer can't be already started."];
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700153 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800154 if ((self = [super init])) {
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700155 _host = [host copy];
156 _path = [path copy];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800157
158 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700159 _callQueue = dispatch_queue_create("io.grpc.call", NULL);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800160
161 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700162
murgatroid9984fa5312015-08-28 10:55:55 -0700163 _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800164 }
165 return self;
166}
167
168#pragma mark Finish
169
170- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700171 @synchronized(self) {
172 _state = GRXWriterStateFinished;
173 }
174
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700175 // If the call isn't retained anywhere else, it can be deallocated now.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700176 _retainSelf = nil;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700177
178 // If there were still request messages coming, stop them.
Jorge Canizales238ad782015-08-07 23:11:29 -0700179 @synchronized(_requestWriter) {
180 _requestWriter.state = GRXWriterStateFinished;
Jorge Canizales238ad782015-08-07 23:11:29 -0700181 }
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700182
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800183 if (errorOrNil) {
184 [_responseWriteable cancelWithError:errorOrNil];
185 } else {
186 [_responseWriteable enqueueSuccessfulCompletion];
187 }
188}
189
190- (void)cancelCall {
191 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700192 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800193}
194
195- (void)cancel {
196 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
197 code:GRPCErrorCodeCancelled
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700198 userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800199 [self cancelCall];
200}
201
202- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700203 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800204 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700205 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800206 });
207}
208
209#pragma mark Read messages
210
211// Only called from the call queue.
212// The handler will be called from the network queue.
murgatroid996cc46802015-04-28 09:35:48 -0700213- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700214 // TODO(jcanizales): Add error handlers for async failures
murgatroid9954e93d42015-04-27 09:29:49 -0700215 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800216}
217
218// Called initially from the network queue once response headers are received,
219// then "recursively" from the responseWriteable queue after each response from the
220// server has been written.
221// If the call is currently paused, this is a noop. Restarting the call will invoke this
222// method.
223// TODO(jcanizales): Rename to readResponseIfNotPaused.
224- (void)startNextRead {
225 if (self.state == GRXWriterStatePaused) {
226 return;
227 }
228 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700229 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700230
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800231 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700232 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
233 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700234 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800235 return;
236 }
murgatroid996cc46802015-04-28 09:35:48 -0700237 NSData *data = [NSData grpc_dataWithByteBuffer:message];
238 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800239 if (!data) {
240 // The app doesn't have enough memory to hold the server response. We
241 // don't want to throw, because the app shouldn't crash for a behavior
242 // that's on the hands of any server to have. Instead we finish and ask
243 // the server to cancel.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800244 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
Muxi Yanfc99ff62016-07-06 15:08:30 -0700245 code:GRPCErrorCodeResourceExhausted
Muxi Yanff6cd702016-07-07 09:57:54 -0700246 userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800247 [weakSelf cancelCall];
248 return;
249 }
Jorge Canizales4c6f7782015-07-17 23:13:36 -0700250 [weakWriteable enqueueValue:data completionHandler:^{
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800251 [weakSelf startNextRead];
252 }];
253 }];
254 });
255}
256
257#pragma mark Send headers
258
Jorge Canizalesf4f150f2015-11-01 22:31:12 -0800259- (void)sendHeaders:(NSDictionary *)headers {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700260 // TODO(jcanizales): Add error handlers for async failures
Jorge Canizales5c339d12015-09-02 17:41:43 -0700261 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] initWithMetadata:headers
Muxi Yan22f79732016-09-30 14:24:56 -0700262 flags:(uint32_t)[GRPCCall getCallFlag:_host path:_path]
Jorge Canizales5c339d12015-09-02 17:41:43 -0700263 handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800264}
265
266#pragma mark GRXWriteable implementation
267
268// Only called from the call queue. The error handler will be called from the
269// network queue if the write didn't succeed.
270- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
271
272 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700273 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700274 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800275 GRPCCall *strongSelf = weakSelf;
276 if (strongSelf) {
Jorge Canizales578ab162015-08-08 17:11:43 -0700277 @synchronized(strongSelf->_requestWriter) {
Jorge Canizales238ad782015-08-07 23:11:29 -0700278 strongSelf->_requestWriter.state = GRXWriterStateStarted;
279 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800280 }
281 };
Jorge Canizales238ad782015-08-07 23:11:29 -0700282 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message
283 handler:resumingHandler]]
284 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800285}
286
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700287- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800288 // TODO(jcanizales): Throw/assert if value isn't NSData.
289
290 // Pause the input and only resume it when the C layer notifies us that writes
291 // can proceed.
Jorge Canizales238ad782015-08-07 23:11:29 -0700292 @synchronized(_requestWriter) {
293 _requestWriter.state = GRXWriterStatePaused;
294 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800295
296 __weak GRPCCall *weakSelf = self;
297 dispatch_async(_callQueue, ^{
298 [weakSelf writeMessage:value withErrorHandler:^{
299 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
300 code:GRPCErrorCodeInternal
301 userInfo:nil]];
302 }];
303 });
304}
305
306// Only called from the call queue. The error handler will be called from the
307// network queue if the requests stream couldn't be closed successfully.
308- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid996cc46802015-04-28 09:35:48 -0700309 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
310 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800311}
312
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700313- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800314 if (errorOrNil) {
315 [self cancel];
316 } else {
317 __weak GRPCCall *weakSelf = self;
318 dispatch_async(_callQueue, ^{
319 [weakSelf finishRequestWithErrorHandler:^{
320 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
321 code:GRPCErrorCodeInternal
322 userInfo:nil]];
323 }];
324 });
325 }
326}
327
328#pragma mark Invoke
329
330// Both handlers will eventually be called, from the network queue. Writes can start immediately
331// after this.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700332// The first one (headersHandler), when the response headers are received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800333// The second one (completionHandler), whenever the RPC finishes for any reason.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700334- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700335 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700336 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700337 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
Jorge Canizales0b34c892015-08-12 20:19:20 -0700338 initWithHandler:headersHandler]]];
murgatroid996cc46802015-04-28 09:35:48 -0700339 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
340 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800341}
342
343- (void)invokeCall {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700344 [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700345 // Response headers received.
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100346 self.responseHeaders = headers;
347 [self startNextRead];
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700348 } completionHandler:^(NSError *error, NSDictionary *trailers) {
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100349 self.responseTrailers = trailers;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700350
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100351 if (error) {
352 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
353 if (error.userInfo) {
354 [userInfo addEntriesFromDictionary:error.userInfo];
Jorge Canizalesc58a1102015-06-15 19:03:41 -0700355 }
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100356 userInfo[kGRPCTrailersKey] = self.responseTrailers;
357 // TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be
358 // called before this one, so an error might end up with trailers but no headers. We
359 // shouldn't call finishWithError until ater both blocks are called. It is also when this is
360 // done that we can provide a merged view of response headers and trailers in a thread-safe
361 // way.
362 if (self.responseHeaders) {
363 userInfo[kGRPCHeadersKey] = self.responseHeaders;
364 }
365 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700366 }
Nicolas "Pixel" Noble70224362016-03-21 22:19:43 +0100367 [self finishWithError:error];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800368 }];
369 // Now that the RPC has been initiated, request writes can start.
Jorge Canizales238ad782015-08-07 23:11:29 -0700370 @synchronized(_requestWriter) {
371 [_requestWriter startWithWriteable:self];
372 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800373}
374
375#pragma mark GRXWriter implementation
376
377- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700378 @synchronized(self) {
379 _state = GRXWriterStateStarted;
380 }
381
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700382 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
383 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
384 // before being autoreleased).
385 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
386 // that the life of the instance is determined by this retain cycle.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700387 _retainSelf = self;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700388
389 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable];
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700390
391 _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path];
392 NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
393
Jorge Canizales6512d262015-08-12 17:33:23 -0700394 [self sendHeaders:_requestHeaders];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800395 [self invokeCall];
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700396 // TODO(jcanizales): Extract this logic somewhere common.
397 NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
398 if (!host) {
399 // TODO(jcanizales): Check this on init.
400 [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host];
401 }
402 __weak typeof(self) weakSelf = self;
403 _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
404 [_connectivityMonitor handleLossWithHandler:^{
405 typeof(self) strongSelf = weakSelf;
406 if (strongSelf) {
407 [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
408 code:GRPCErrorCodeUnavailable
409 userInfo:@{NSLocalizedDescriptionKey: @"Connectivity lost."}]];
Jorge Canizales3785d532016-06-28 10:48:20 -0700410 [[GRPCHost hostWithAddress:strongSelf->_host] disconnect];
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700411 }
412 }];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800413}
414
415- (void)setState:(GRXWriterState)newState {
Jorge Canizales0803bb02016-04-30 10:40:18 -0700416 @synchronized(self) {
417 // Manual transitions are only allowed from the started or paused states.
418 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
419 return;
420 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800421
Jorge Canizales0803bb02016-04-30 10:40:18 -0700422 switch (newState) {
423 case GRXWriterStateFinished:
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800424 _state = newState;
Jorge Canizales0803bb02016-04-30 10:40:18 -0700425 // Per GRXWriter's contract, setting the state to Finished manually
426 // means one doesn't wish the writeable to be messaged anymore.
427 [_responseWriteable cancelSilently];
428 _responseWriteable = nil;
429 return;
430 case GRXWriterStatePaused:
431 _state = newState;
432 return;
433 case GRXWriterStateStarted:
434 if (_state == GRXWriterStatePaused) {
435 _state = newState;
436 [self startNextRead];
437 }
438 return;
439 case GRXWriterStateNotStarted:
440 return;
441 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800442 }
443}
Jorge Canizalesf1d084a2015-10-30 11:14:09 -0700444
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800445@end