blob: afa01e2f0feedfe5e6f48fc4183b74d143cc8841 [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"
murgatroid9984fa5312015-08-28 10:55:55 -070044#import "private/GRPCRequestHeaders.h"
Jorge Canizales5e0efd92015-02-17 18:23:58 -080045
Jorge Canizales1ab2a712015-08-12 20:32:11 -070046NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
47NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -070048
Jorge Canizales5e0efd92015-02-17 18:23:58 -080049@interface GRPCCall () <GRXWriteable>
Jorge Canizales0b34c892015-08-12 20:19:20 -070050// Make them read-write.
51@property(atomic, strong) NSDictionary *responseHeaders;
52@property(atomic, strong) NSDictionary *responseTrailers;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080053@end
54
55// The following methods of a C gRPC call object aren't reentrant, and thus
56// calls to them must be serialized:
murgatroid99b5c076f2015-04-27 17:25:36 -070057// - start_batch
Jorge Canizales5e0efd92015-02-17 18:23:58 -080058// - destroy
Jorge Canizales5e0efd92015-02-17 18:23:58 -080059//
murgatroid99b5c076f2015-04-27 17:25:36 -070060// start_batch with a SEND_MESSAGE argument can only be called after the
61// OP_COMPLETE event for any previous write is received. This is achieved by
Jorge Canizales5e0efd92015-02-17 18:23:58 -080062// pausing the requests writer immediately every time it writes a value, and
murgatroid99b5c076f2015-04-27 17:25:36 -070063// resuming it again when OP_COMPLETE is received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080064//
murgatroid99b5c076f2015-04-27 17:25:36 -070065// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
66// the OP_COMPLETE event for any previous read is received.This is easier to
67// enforce, as we're writing the received messages into the writeable:
68// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
69// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
70// each RECV_MESSAGE batch.
Jorge Canizales5e0efd92015-02-17 18:23:58 -080071@implementation GRPCCall {
72 dispatch_queue_t _callQueue;
73
murgatroid9930b7d4e2015-04-24 10:36:43 -070074 GRPCWrappedCall *_wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080075 dispatch_once_t _callAlreadyInvoked;
76
Jorge Canizales5e0efd92015-02-17 18:23:58 -080077 // The C gRPC library has less guarantees on the ordering of events than we
78 // do. Particularly, in the face of errors, there's no ordering guarantee at
79 // all. This wrapper over our actual writeable ensures thread-safety and
80 // correct ordering.
Jorge Canizales35f003b2015-07-17 21:14:36 -070081 GRXConcurrentWriteable *_responseWriteable;
Jorge Canizales238ad782015-08-07 23:11:29 -070082
83 // The network thread wants the requestWriter to resume (when the server is ready for more input),
84 // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
85 // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
86 // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
87 // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
88 // pause the writer immediately on writeValue:, so we need our locking to be recursive.
Jorge Canizales56047122015-07-17 12:18:08 -070089 GRXWriter *_requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -070090
Jorge Canizales6531b2b2015-07-18 00:19:14 -070091 // To create a retain cycle when a call is started, up until it finishes. See
Jorge Canizaleseb87b462015-08-08 16:16:43 -070092 // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
93 // reference to the call object if all they're interested in is the handler being executed when
94 // the response arrives.
95 GRPCCall *_retainSelf;
Jorge Canizales6531b2b2015-07-18 00:19:14 -070096
murgatroid9984fa5312015-08-28 10:55:55 -070097 GRPCRequestHeaders *_requestHeaders;
Jorge Canizales5e0efd92015-02-17 18:23:58 -080098}
99
100@synthesize state = _state;
101
102- (instancetype)init {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700103 return [self initWithHost:nil path:nil requestsWriter:nil];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800104}
105
106// Designated initializer
107- (instancetype)initWithHost:(NSString *)host
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700108 path:(NSString *)path
Jorge Canizales56047122015-07-17 12:18:08 -0700109 requestsWriter:(GRXWriter *)requestWriter {
Jorge Canizalesbe808e32015-07-04 14:37:58 -0700110 if (!host || !path) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700111 [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800112 }
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700113 if (requestWriter.state != GRXWriterStateNotStarted) {
Jorge Canizales597ef982015-07-31 23:31:56 -0700114 [NSException raise:NSInvalidArgumentException
115 format:@"The requests writer can't be already started."];
Jorge Canizales6bbfcc32015-06-17 14:10:52 -0700116 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800117 if ((self = [super init])) {
Jorge Canizales3a5253e2015-07-31 23:48:56 -0700118 _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:host path:path];
Jorge Canizales8c5318a2015-08-05 18:50:08 -0700119 if (!_wrappedCall) {
120 return nil;
121 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800122
123 // Serial queue to invoke the non-reentrant methods of the grpc_call object.
124 _callQueue = dispatch_queue_create("org.grpc.call", NULL);
125
126 _requestWriter = requestWriter;
Jorge Canizales544963e2015-06-12 19:46:27 -0700127
murgatroid9984fa5312015-08-28 10:55:55 -0700128 _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800129 }
130 return self;
131}
132
Jorge Canizales544963e2015-06-12 19:46:27 -0700133#pragma mark Metadata
134
murgatroid9984fa5312015-08-28 10:55:55 -0700135- (GRPCRequestHeaders *)requestHeaders {
Jorge Canizales6512d262015-08-12 17:33:23 -0700136 return _requestHeaders;
Jorge Canizales544963e2015-06-12 19:46:27 -0700137}
138
Jorge Canizales6512d262015-08-12 17:33:23 -0700139- (void)setRequestHeaders:(NSDictionary *)requestHeaders {
murgatroid9984fa5312015-08-28 10:55:55 -0700140 GRPCRequestHeaders *newHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
141 for (id key in requestHeaders) {
142 newHeaders[key] = requestHeaders[key];
143 }
144 _requestHeaders = newHeaders;
Jorge Canizales544963e2015-06-12 19:46:27 -0700145}
146
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800147#pragma mark Finish
148
149- (void)finishWithError:(NSError *)errorOrNil {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700150 // If the call isn't retained anywhere else, it can be deallocated now.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700151 _retainSelf = nil;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700152
153 // If there were still request messages coming, stop them.
Jorge Canizales238ad782015-08-07 23:11:29 -0700154 @synchronized(_requestWriter) {
155 _requestWriter.state = GRXWriterStateFinished;
Jorge Canizales238ad782015-08-07 23:11:29 -0700156 }
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700157
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800158 if (errorOrNil) {
159 [_responseWriteable cancelWithError:errorOrNil];
160 } else {
161 [_responseWriteable enqueueSuccessfulCompletion];
162 }
163}
164
165- (void)cancelCall {
166 // Can be called from any thread, any number of times.
murgatroid99b56609c2015-04-28 16:41:11 -0700167 [_wrappedCall cancel];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800168}
169
170- (void)cancel {
171 [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
172 code:GRPCErrorCodeCancelled
173 userInfo:nil]];
174 [self cancelCall];
175}
176
177- (void)dealloc {
murgatroid996cc46802015-04-28 09:35:48 -0700178 __block GRPCWrappedCall *wrappedCall = _wrappedCall;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800179 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700180 wrappedCall = nil;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800181 });
182}
183
184#pragma mark Read messages
185
186// Only called from the call queue.
187// The handler will be called from the network queue.
murgatroid996cc46802015-04-28 09:35:48 -0700188- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700189 // TODO(jcanizales): Add error handlers for async failures
murgatroid9954e93d42015-04-27 09:29:49 -0700190 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800191}
192
193// Called initially from the network queue once response headers are received,
194// then "recursively" from the responseWriteable queue after each response from the
195// server has been written.
196// If the call is currently paused, this is a noop. Restarting the call will invoke this
197// method.
198// TODO(jcanizales): Rename to readResponseIfNotPaused.
199- (void)startNextRead {
200 if (self.state == GRXWriterStatePaused) {
201 return;
202 }
203 __weak GRPCCall *weakSelf = self;
Jorge Canizales35f003b2015-07-17 21:14:36 -0700204 __weak GRXConcurrentWriteable *weakWriteable = _responseWriteable;
murgatroid9969927d62015-04-24 13:32:48 -0700205
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800206 dispatch_async(_callQueue, ^{
murgatroid996cc46802015-04-28 09:35:48 -0700207 [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) {
208 if (message == NULL) {
murgatroid99b56609c2015-04-28 16:41:11 -0700209 // No more messages from the server
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800210 return;
211 }
murgatroid996cc46802015-04-28 09:35:48 -0700212 NSData *data = [NSData grpc_dataWithByteBuffer:message];
213 grpc_byte_buffer_destroy(message);
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800214 if (!data) {
215 // The app doesn't have enough memory to hold the server response. We
216 // don't want to throw, because the app shouldn't crash for a behavior
217 // that's on the hands of any server to have. Instead we finish and ask
218 // the server to cancel.
219 //
220 // TODO(jcanizales): No canonical code is appropriate for this situation
221 // (because it's just a client problem). Use another domain and an
222 // appropriately-documented code.
223 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
224 code:GRPCErrorCodeInternal
225 userInfo:nil]];
226 [weakSelf cancelCall];
227 return;
228 }
Jorge Canizales4c6f7782015-07-17 23:13:36 -0700229 [weakWriteable enqueueValue:data completionHandler:^{
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800230 [weakSelf startNextRead];
231 }];
232 }];
233 });
234}
235
236#pragma mark Send headers
237
murgatroid9984fa5312015-08-28 10:55:55 -0700238- (void)sendHeaders:(GRPCRequestHeaders *)headers {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700239 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700240 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
murgatroid9984fa5312015-08-28 10:55:55 -0700241 initWithMetadata:[headers asDictionary] ?: @{}
242 handler:nil]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800243}
244
245#pragma mark GRXWriteable implementation
246
247// Only called from the call queue. The error handler will be called from the
248// network queue if the write didn't succeed.
249- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler {
250
251 __weak GRPCCall *weakSelf = self;
murgatroid9954e93d42015-04-27 09:29:49 -0700252 void(^resumingHandler)(void) = ^{
murgatroid99ca38ddb2015-04-29 13:16:42 -0700253 // Resume the request writer.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800254 GRPCCall *strongSelf = weakSelf;
255 if (strongSelf) {
Jorge Canizales578ab162015-08-08 17:11:43 -0700256 @synchronized(strongSelf->_requestWriter) {
Jorge Canizales238ad782015-08-07 23:11:29 -0700257 strongSelf->_requestWriter.state = GRXWriterStateStarted;
258 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800259 }
260 };
Jorge Canizales238ad782015-08-07 23:11:29 -0700261 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message
262 handler:resumingHandler]]
263 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800264}
265
Jorge Canizalesa90a9c32015-05-18 17:12:41 -0700266- (void)writeValue:(id)value {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800267 // TODO(jcanizales): Throw/assert if value isn't NSData.
268
269 // Pause the input and only resume it when the C layer notifies us that writes
270 // can proceed.
Jorge Canizales238ad782015-08-07 23:11:29 -0700271 @synchronized(_requestWriter) {
272 _requestWriter.state = GRXWriterStatePaused;
273 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800274
275 __weak GRPCCall *weakSelf = self;
276 dispatch_async(_callQueue, ^{
277 [weakSelf writeMessage:value withErrorHandler:^{
278 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
279 code:GRPCErrorCodeInternal
280 userInfo:nil]];
281 }];
282 });
283}
284
285// Only called from the call queue. The error handler will be called from the
286// network queue if the requests stream couldn't be closed successfully.
287- (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
murgatroid996cc46802015-04-28 09:35:48 -0700288 [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]]
289 errorHandler:errorHandler];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800290}
291
Jorge Canizalesb2c300c2015-05-18 17:19:16 -0700292- (void)writesFinishedWithError:(NSError *)errorOrNil {
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800293 if (errorOrNil) {
294 [self cancel];
295 } else {
296 __weak GRPCCall *weakSelf = self;
297 dispatch_async(_callQueue, ^{
298 [weakSelf finishRequestWithErrorHandler:^{
299 [weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
300 code:GRPCErrorCodeInternal
301 userInfo:nil]];
302 }];
303 });
304 }
305}
306
307#pragma mark Invoke
308
309// Both handlers will eventually be called, from the network queue. Writes can start immediately
310// after this.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700311// The first one (headersHandler), when the response headers are received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800312// The second one (completionHandler), whenever the RPC finishes for any reason.
Jorge Canizales0b34c892015-08-12 20:19:20 -0700313- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700314 completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
murgatroid99ca38ddb2015-04-29 13:16:42 -0700315 // TODO(jcanizales): Add error handlers for async failures
murgatroid996cc46802015-04-28 09:35:48 -0700316 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
Jorge Canizales0b34c892015-08-12 20:19:20 -0700317 initWithHandler:headersHandler]]];
murgatroid996cc46802015-04-28 09:35:48 -0700318 [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
319 initWithHandler:completionHandler]]];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800320}
321
322- (void)invokeCall {
323 __weak GRPCCall *weakSelf = self;
Jorge Canizales0b34c892015-08-12 20:19:20 -0700324 [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700325 // Response headers received.
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800326 GRPCCall *strongSelf = weakSelf;
327 if (strongSelf) {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700328 strongSelf.responseHeaders = headers;
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800329 [strongSelf startNextRead];
330 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700331 } completionHandler:^(NSError *error, NSDictionary *trailers) {
332 GRPCCall *strongSelf = weakSelf;
333 if (strongSelf) {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700334 strongSelf.responseTrailers = trailers;
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700335
Jorge Canizalesc58a1102015-06-15 19:03:41 -0700336 if (error) {
Jorge Canizales0b34c892015-08-12 20:19:20 -0700337 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
338 if (error.userInfo) {
339 [userInfo addEntriesFromDictionary:error.userInfo];
340 }
Jorge Canizales1ab2a712015-08-12 20:32:11 -0700341 userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers;
Jorge Canizales0b34c892015-08-12 20:19:20 -0700342 // TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be
343 // called before this one, so an error might end up with trailers but no headers. We
Jorge Canizales1ab2a712015-08-12 20:32:11 -0700344 // shouldn't call finishWithError until ater both blocks are called. It is also when this is
345 // done that we can provide a merged view of response headers and trailers in a thread-safe
346 // way.
347 if (strongSelf.responseHeaders) {
348 userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders;
349 }
Jorge Canizalesc58a1102015-06-15 19:03:41 -0700350 error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
351 }
Jorge Canizalesf3a4f2c2015-06-12 22:12:50 -0700352 [strongSelf finishWithError:error];
353 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800354 }];
355 // Now that the RPC has been initiated, request writes can start.
Jorge Canizales238ad782015-08-07 23:11:29 -0700356 @synchronized(_requestWriter) {
357 [_requestWriter startWithWriteable:self];
358 }
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800359}
360
361#pragma mark GRXWriter implementation
362
363- (void)startWithWriteable:(id<GRXWriteable>)writeable {
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700364 // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
365 // This makes RPCs in which the call isn't externally retained possible (as long as it is started
366 // before being autoreleased).
367 // Care is taken not to retain self strongly in any of the blocks used in this implementation, so
368 // that the life of the instance is determined by this retain cycle.
Jorge Canizaleseb87b462015-08-08 16:16:43 -0700369 _retainSelf = self;
Jorge Canizales6531b2b2015-07-18 00:19:14 -0700370
371 _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable];
Jorge Canizales6512d262015-08-12 17:33:23 -0700372 [self sendHeaders:_requestHeaders];
Jorge Canizales5e0efd92015-02-17 18:23:58 -0800373 [self invokeCall];
374}
375
376- (void)setState:(GRXWriterState)newState {
377 // Manual transitions are only allowed from the started or paused states.
378 if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
379 return;
380 }
381
382 switch (newState) {
383 case GRXWriterStateFinished:
384 _state = newState;
385 // Per GRXWriter's contract, setting the state to Finished manually
386 // means one doesn't wish the writeable to be messaged anymore.
387 [_responseWriteable cancelSilently];
388 _responseWriteable = nil;
389 return;
390 case GRXWriterStatePaused:
391 _state = newState;
392 return;
393 case GRXWriterStateStarted:
394 if (_state == GRXWriterStatePaused) {
395 _state = newState;
396 [self startNextRead];
397 }
398 return;
399 case GRXWriterStateNotStarted:
400 return;
401 }
402}
403@end