|
|
|
@ -108,6 +108,9 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
// The dispatch queue to be used for enqueuing responses to user. Defaulted to the main dispatch |
|
|
|
|
// queue |
|
|
|
|
dispatch_queue_t _responseQueue; |
|
|
|
|
|
|
|
|
|
// Whether the call is finished. If it is, should not call finishWithError again. |
|
|
|
|
BOOL _finished; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@synthesize state = _state; |
|
|
|
@ -206,6 +209,8 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
} else { |
|
|
|
|
[_responseWriteable enqueueSuccessfulCompletion]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[GRPCConnectivityMonitor unregisterObserver:self]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)cancelCall { |
|
|
|
@ -214,9 +219,10 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)cancel { |
|
|
|
|
[self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeCancelled |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]]; |
|
|
|
|
[self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeCancelled |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]]; |
|
|
|
|
|
|
|
|
|
if (!self.isWaitingForToken) { |
|
|
|
|
[self cancelCall]; |
|
|
|
|
} else { |
|
|
|
@ -224,6 +230,19 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)maybeFinishWithError:(NSError *)errorOrNil { |
|
|
|
|
BOOL toFinish = NO; |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (_finished == NO) { |
|
|
|
|
_finished = YES; |
|
|
|
|
toFinish = YES; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (toFinish == YES) { |
|
|
|
|
[self finishWithError:errorOrNil]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)dealloc { |
|
|
|
|
__block GRPCWrappedCall *wrappedCall = _wrappedCall; |
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
@ -250,11 +269,13 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
if (self.state == GRXWriterStatePaused) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
__weak GRXConcurrentWriteable *weakWriteable = _responseWriteable; |
|
|
|
|
|
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
[weakSelf startReadWithHandler:^(grpc_byte_buffer *message) { |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
__weak GRXConcurrentWriteable *weakWriteable = self->_responseWriteable; |
|
|
|
|
[self startReadWithHandler:^(grpc_byte_buffer *message) { |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
__strong GRXConcurrentWriteable *strongWriteable = weakWriteable; |
|
|
|
|
if (message == NULL) { |
|
|
|
|
// No more messages from the server |
|
|
|
|
return; |
|
|
|
@ -266,14 +287,14 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
// don't want to throw, because the app shouldn't crash for a behavior |
|
|
|
|
// that's on the hands of any server to have. Instead we finish and ask |
|
|
|
|
// the server to cancel. |
|
|
|
|
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeResourceExhausted |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]]; |
|
|
|
|
[weakSelf cancelCall]; |
|
|
|
|
[strongSelf maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeResourceExhausted |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]]; |
|
|
|
|
[strongSelf cancelCall]; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
[weakWriteable enqueueValue:data completionHandler:^{ |
|
|
|
|
[weakSelf startNextRead]; |
|
|
|
|
[strongWriteable enqueueValue:data completionHandler:^{ |
|
|
|
|
[strongSelf startNextRead]; |
|
|
|
|
}]; |
|
|
|
|
}]; |
|
|
|
|
}); |
|
|
|
@ -333,12 +354,17 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
_requestWriter.state = GRXWriterStatePaused; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
[weakSelf writeMessage:value withErrorHandler:^{ |
|
|
|
|
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeInternal |
|
|
|
|
userInfo:nil]]; |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
[self writeMessage:value withErrorHandler:^{ |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf != nil) { |
|
|
|
|
[strongSelf maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeInternal |
|
|
|
|
userInfo:nil]]; |
|
|
|
|
// Wrapped call must be canceled when error is reported to upper layers |
|
|
|
|
[strongSelf cancelCall]; |
|
|
|
|
} |
|
|
|
|
}]; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
@ -360,12 +386,15 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
if (errorOrNil) { |
|
|
|
|
[self cancel]; |
|
|
|
|
} else { |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
[weakSelf finishRequestWithErrorHandler:^{ |
|
|
|
|
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeInternal |
|
|
|
|
userInfo:nil]]; |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
[self finishRequestWithErrorHandler:^{ |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
[strongSelf maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeInternal |
|
|
|
|
userInfo:nil]]; |
|
|
|
|
// Wrapped call must be canceled when error is reported to upper layers |
|
|
|
|
[strongSelf cancelCall]; |
|
|
|
|
}]; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
@ -387,30 +416,37 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)invokeCall { |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
[self invokeCallWithHeadersHandler:^(NSDictionary *headers) { |
|
|
|
|
// Response headers received. |
|
|
|
|
self.responseHeaders = headers; |
|
|
|
|
[self startNextRead]; |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
strongSelf.responseHeaders = headers; |
|
|
|
|
[strongSelf startNextRead]; |
|
|
|
|
} |
|
|
|
|
} completionHandler:^(NSError *error, NSDictionary *trailers) { |
|
|
|
|
self.responseTrailers = trailers; |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
strongSelf.responseTrailers = trailers; |
|
|
|
|
|
|
|
|
|
if (error) { |
|
|
|
|
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; |
|
|
|
|
if (error.userInfo) { |
|
|
|
|
[userInfo addEntriesFromDictionary:error.userInfo]; |
|
|
|
|
} |
|
|
|
|
userInfo[kGRPCTrailersKey] = self.responseTrailers; |
|
|
|
|
// TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be |
|
|
|
|
// called before this one, so an error might end up with trailers but no headers. We |
|
|
|
|
// shouldn't call finishWithError until ater both blocks are called. It is also when this is |
|
|
|
|
// done that we can provide a merged view of response headers and trailers in a thread-safe |
|
|
|
|
// way. |
|
|
|
|
if (self.responseHeaders) { |
|
|
|
|
userInfo[kGRPCHeadersKey] = self.responseHeaders; |
|
|
|
|
if (error) { |
|
|
|
|
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; |
|
|
|
|
if (error.userInfo) { |
|
|
|
|
[userInfo addEntriesFromDictionary:error.userInfo]; |
|
|
|
|
} |
|
|
|
|
userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers; |
|
|
|
|
// TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be |
|
|
|
|
// called before this one, so an error might end up with trailers but no headers. We |
|
|
|
|
// shouldn't call finishWithError until ater both blocks are called. It is also when this is |
|
|
|
|
// done that we can provide a merged view of response headers and trailers in a thread-safe |
|
|
|
|
// way. |
|
|
|
|
if (strongSelf.responseHeaders) { |
|
|
|
|
userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders; |
|
|
|
|
} |
|
|
|
|
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; |
|
|
|
|
} |
|
|
|
|
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; |
|
|
|
|
[strongSelf maybeFinishWithError:error]; |
|
|
|
|
} |
|
|
|
|
[self finishWithError:error]; |
|
|
|
|
}]; |
|
|
|
|
// Now that the RPC has been initiated, request writes can start. |
|
|
|
|
@synchronized(_requestWriter) { |
|
|
|
@ -439,16 +475,8 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
// TODO(jcanizales): Check this on init. |
|
|
|
|
[NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host]; |
|
|
|
|
} |
|
|
|
|
_connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host]; |
|
|
|
|
__weak typeof(self) weakSelf = self; |
|
|
|
|
void (^handler)(void) = ^{ |
|
|
|
|
typeof(self) strongSelf = weakSelf; |
|
|
|
|
[strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]]; |
|
|
|
|
}; |
|
|
|
|
[_connectivityMonitor handleLossWithHandler:handler |
|
|
|
|
wifiStatusChangeHandler:nil]; |
|
|
|
|
[GRPCConnectivityMonitor registerObserver:self |
|
|
|
|
selector:@selector(connectivityChanged:)]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)startWithWriteable:(id<GRXWriteable>)writeable { |
|
|
|
@ -512,4 +540,12 @@ static NSString * const kBearerPrefix = @"Bearer "; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)connectivityChanged:(NSNotification *)note { |
|
|
|
|
[self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ NSLocalizedDescriptionKey : @"Connectivity lost." }]]; |
|
|
|
|
// Cancel underlying call upon this notification |
|
|
|
|
[self cancelCall]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@end |
|
|
|
|