diff options
author | Jan Tattermusch <jtattermusch@users.noreply.github.com> | 2016-03-11 14:35:37 -0800 |
---|---|---|
committer | Jan Tattermusch <jtattermusch@users.noreply.github.com> | 2016-03-11 14:35:37 -0800 |
commit | 7e6b2da508382aa8370248ba02ae773e58769d1f (patch) | |
tree | 2bce84ce5252f2ba39f48d457a12ab5b5c649d8e | |
parent | 0561cd89d23855556927d5f341a719378953a404 (diff) | |
parent | 8b414f184db9295e30164140d54357c6ad310368 (diff) |
Merge pull request #5720 from jcanizales/reachability-objc-fix
Reachability ObjC fix
9 files changed, 453 insertions, 60 deletions
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index f79b7d0bc0..2d45818b6e 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -37,6 +37,8 @@ #include <grpc/support/time.h> #import <RxLibrary/GRXConcurrentWriteable.h> +#import "private/GRPCConnectivityMonitor.h" +#import "private/GRPCHost.h" #import "private/GRPCRequestHeaders.h" #import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" @@ -71,8 +73,11 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; @implementation GRPCCall { dispatch_queue_t _callQueue; + NSString *_host; + NSString *_path; GRPCWrappedCall *_wrappedCall; dispatch_once_t _callAlreadyInvoked; + GRPCConnectivityMonitor *_connectivityMonitor; // The C gRPC library has less guarantees on the ordering of events than we // do. Particularly, in the face of errors, there's no ordering guarantee at @@ -115,13 +120,11 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; format:@"The requests writer can't be already started."]; } if ((self = [super init])) { - _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:host path:path]; - if (!_wrappedCall) { - return nil; - } + _host = [host copy]; + _path = [path copy]; // Serial queue to invoke the non-reentrant methods of the grpc_call object. - _callQueue = dispatch_queue_create("org.grpc.call", NULL); + _callQueue = dispatch_queue_create("io.grpc.call", NULL); _requestWriter = requestWriter; @@ -156,7 +159,7 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; - (void)cancel { [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain code:GRPCErrorCodeCancelled - userInfo:nil]]; + userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]]; [self cancelCall]; } @@ -354,8 +357,29 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; _retainSelf = self; _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable]; + + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path]; + NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); + [self sendHeaders:_requestHeaders]; [self invokeCall]; + // TODO(jcanizales): Extract this logic somewhere common. + NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host; + if (!host) { + // TODO(jcanizales): Check this on init. + [NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host]; + } + __weak typeof(self) weakSelf = self; + _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host]; + [_connectivityMonitor handleLossWithHandler:^{ + typeof(self) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeUnavailable + userInfo:@{NSLocalizedDescriptionKey: @"Connectivity lost."}]]; + [[GRPCHost hostWithAddress:strongSelf->_host] disconnect]; + } + }]; } - (void)setState:(GRXWriterState)newState { @@ -385,4 +409,5 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; return; } } + @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h index 8661ae6f97..e49a6aca29 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCChannel.h @@ -35,6 +35,7 @@ #include <grpc/grpc.h> +@class GRPCCompletionQueue; struct grpc_channel_credentials; @@ -80,4 +81,6 @@ struct grpc_channel_credentials; + (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host channelArgs:(nullable NSDictionary *)channelArgs; +- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path + completionQueue:(nonnull GRPCCompletionQueue *)queue; @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index 7e55a473d7..d7de025e21 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -38,6 +38,8 @@ #include <grpc/support/log.h> #include <grpc/support/string_util.h> +#import "GRPCCompletionQueue.h" + /** * Returns @c grpc_channel_credentials from the specified @c path. If the file at the path could not * be read then NULL is returned. If NULL is returned, @c errorPtr may not be NULL if there are @@ -205,4 +207,16 @@ grpc_channel_args * buildChannelArgs(NSDictionary *dictionary) { channelArgs:channelArgs]; } +- (grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue { + return grpc_channel_create_call(_unmanagedChannel, + NULL, GRPC_PROPAGATE_DEFAULTS, + queue.unmanagedQueue, + path.UTF8String, + // Get "host" from "host:port" + // TODO(jcanizales): Use NSURLs throughout, to clarify these. + [_host componentsSeparatedByString:@":"][0].UTF8String, + gpr_inf_future(GPR_CLOCK_REALTIME), NULL); +} + @end diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h new file mode 100644 index 0000000000..2fae410331 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h @@ -0,0 +1,77 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import <Foundation/Foundation.h> +#import <SystemConfiguration/SystemConfiguration.h> + +@interface GRPCReachabilityFlags : NSObject + ++ (nonnull instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags; + +/** + * One accessor method to query each of the different flags. Example: + +@property(nonatomic, readonly) BOOL isCell; + + */ +#define GRPC_XMACRO_ITEM(methodName, FlagName) \ +@property(nonatomic, readonly) BOOL methodName; + +#include "GRPCReachabilityFlagNames.xmacro.h" +#undef GRPC_XMACRO_ITEM + +@property(nonatomic, readonly) BOOL isHostReachable; +@end + + +@interface GRPCConnectivityMonitor : NSObject + ++ (nullable instancetype)monitorWithHost:(nonnull NSString *)hostName; + +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** + * Queue on which callbacks will be dispatched. Default is the main queue. Set it before calling + * handleLossWithHandler:. + */ +// TODO(jcanizales): Default to a serial background queue instead. +@property(nonatomic, strong, null_resettable) dispatch_queue_t queue; + +/** + * Calls handler every time the connectivity to this instance's host is lost. If this instance is + * released before that happens, the handler won't be called. + * Only one handler is active at a time, so if this method is called again before the previous + * handler has been called, it might never be called at all (or yes, if it has already been queued). + */ +- (void)handleLossWithHandler:(nonnull void (^)())handler; +@end diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m new file mode 100644 index 0000000000..b4061bd5ef --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m @@ -0,0 +1,192 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import "GRPCConnectivityMonitor.h" + +#pragma mark Flags + +@implementation GRPCReachabilityFlags { + SCNetworkReachabilityFlags _flags; +} + ++ (instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags { + return [[self alloc] initWithFlags:flags]; +} + +- (instancetype)initWithFlags:(SCNetworkReachabilityFlags)flags { + if ((self = [super init])) { + _flags = flags; + } + return self; +} + +/* + * One accessor method implementation per flag. Example: + +- (BOOL)isCell { \ + return !!(_flags & kSCNetworkReachabilityFlagsIsWWAN); \ +} + + */ +#define GRPC_XMACRO_ITEM(methodName, FlagName) \ +- (BOOL)methodName { \ + return !!(_flags & kSCNetworkReachabilityFlags ## FlagName); \ +} +#include "GRPCReachabilityFlagNames.xmacro.h" +#undef GRPC_XMACRO_ITEM + +- (BOOL)isHostReachable { + // Note: connectionOnDemand means it'll be reachable only if using the CFSocketStream API or APIs + // on top of it. + // connectionRequired means we can't tell until a connection is attempted (e.g. for VPN on + // demand). + return self.reachable && !self.interventionRequired && !self.connectionOnDemand; +} + +- (NSString *)description { + NSMutableArray *activeOptions = [NSMutableArray arrayWithCapacity:9]; + + /* + * For each flag, add its name to the array if it's ON. Example: + + if (self.isCell) { + [activeOptions addObject:@"isCell"]; + } + + */ +#define GRPC_XMACRO_ITEM(methodName, FlagName) \ + if (self.methodName) { \ + [activeOptions addObject:@#methodName]; \ + } +#include "GRPCReachabilityFlagNames.xmacro.h" +#undef GRPC_XMACRO_ITEM + + return activeOptions.count == 0 ? @"(none)" : [activeOptions componentsJoinedByString:@", "]; +} + +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[GRPCReachabilityFlags class]] && + _flags == ((GRPCReachabilityFlags *)object)->_flags; +} + +- (NSUInteger)hash { + return _flags; +} +@end + +#pragma mark Connectivity Monitor + +// Assumes the third argument is a block that accepts a GRPCReachabilityFlags object, and passes the +// received ones to it. +static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, + SCNetworkReachabilityFlags flags, + void *info) { + #pragma unused (target) + // This can be called many times with the same info. The info is retained by SCNetworkReachability + // while this function is being executed. + void (^handler)(GRPCReachabilityFlags *) = (__bridge void (^)(GRPCReachabilityFlags *))info; + handler([[GRPCReachabilityFlags alloc] initWithFlags:flags]); +} + +@implementation GRPCConnectivityMonitor { + SCNetworkReachabilityRef _reachabilityRef; +} + +- (nullable instancetype)initWithReachability:(nullable SCNetworkReachabilityRef)reachability { + if (!reachability) { + return nil; + } + if ((self = [super init])) { + _reachabilityRef = CFRetain(reachability); + _queue = dispatch_get_main_queue(); + } + return self; +} + ++ (nullable instancetype)monitorWithHost:(nonnull NSString *)host { + const char *hostName = host.UTF8String; + if (!hostName) { + [NSException raise:NSInvalidArgumentException + format:@"host.UTF8String returns NULL for %@", host]; + } + SCNetworkReachabilityRef reachability = + SCNetworkReachabilityCreateWithName(NULL, hostName); + + GRPCConnectivityMonitor *returnValue = [[self alloc] initWithReachability:reachability]; + if (reachability) { + CFRelease(reachability); + } + return returnValue; +} + +- (void)handleLossWithHandler:(void (^)())handler { + [self startListeningWithHandler:^(GRPCReachabilityFlags *flags) { + if (!flags.isHostReachable) { + handler(); + } + }]; +} + +- (void)startListeningWithHandler:(void (^)(GRPCReachabilityFlags *))handler { + // Copy to ensure the handler block is in the heap (and so can't be deallocated when this method + // returns). + void (^copiedHandler)(GRPCReachabilityFlags *) = [handler copy]; + SCNetworkReachabilityContext context = { + .version = 0, + .info = (__bridge void *)copiedHandler, + .retain = CFRetain, + .release = CFRelease, + }; + // The following will retain context.info, and release it when the callback is set to NULL. + SCNetworkReachabilitySetCallback(_reachabilityRef, PassFlagsToContextInfoBlock, &context); + SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _queue); +} + +- (void)stopListening { + // This releases the block on context.info. + SCNetworkReachabilitySetCallback(_reachabilityRef, NULL, NULL); + SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, NULL); +} + +- (void)setQueue:(dispatch_queue_t)queue { + _queue = queue ?: dispatch_get_main_queue(); +} + +- (void)dealloc { + if (_reachabilityRef) { + [self stopListening]; + CFRelease(_reachabilityRef); + } +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h index 82c0ad6cf6..987d3e9f59 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.h +++ b/src/objective-c/GRPCClient/private/GRPCHost.h @@ -33,27 +33,39 @@ #import <Foundation/Foundation.h> +NS_ASSUME_NONNULL_BEGIN + @class GRPCCompletionQueue; struct grpc_call; @interface GRPCHost : NSObject @property(nonatomic, readonly) NSString *address; -@property(nonatomic, copy) NSString *userAgentPrefix; +@property(nonatomic, copy, nullable) NSString *userAgentPrefix; /** The following properties should only be modified for testing: */ @property(nonatomic, getter=isSecure) BOOL secure; -@property(nonatomic, copy) NSString *pathToCertificates; -@property(nonatomic, copy) NSString *hostNameOverride; +@property(nonatomic, copy, nullable) NSString *pathToCertificates; +@property(nonatomic, copy, nullable) NSString *hostNameOverride; +- (nullable instancetype)init NS_UNAVAILABLE; /** Host objects initialized with the same address are the same. */ -+ (instancetype)hostWithAddress:(NSString *)address; -- (instancetype)initWithAddress:(NSString *)address NS_DESIGNATED_INITIALIZER; ++ (nullable instancetype)hostWithAddress:(NSString *)address; +- (nullable instancetype)initWithAddress:(NSString *)address NS_DESIGNATED_INITIALIZER; /** Create a grpc_call object to the provided path on this host. */ -- (struct grpc_call *)unmanagedCallWithPath:(NSString *)path - completionQueue:(GRPCCompletionQueue *)queue; +- (nullable struct grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue; +// TODO: There's a race when a new RPC is coming through just as an existing one is getting +// notified that there's no connectivity. If connectivity comes back at that moment, the new RPC +// will have its channel destroyed by the other RPC, and will never get notified of a problem, so +// it'll hang (the C layer logs a timeout, with exponential back off). One solution could be to pass +// the GRPCChannel to the GRPCCall, renaming this as "disconnectChannel:channel", which would only +// act on that specific channel. +- (void)disconnect; @end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m index eb1db899b7..508cb20644 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.m +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -34,33 +34,30 @@ #import "GRPCHost.h" #include <grpc/grpc.h> +#import <GRPCClient/GRPCCall.h> #import <GRPCClient/GRPCCall+ChannelArg.h> #import "GRPCChannel.h" #import "GRPCCompletionQueue.h" #import "NSDictionary+GRPC.h" +NS_ASSUME_NONNULL_BEGIN + // TODO(jcanizales): Generate the version in a standalone header, from templates. Like // templates/src/core/surface/version.c.template . #define GRPC_OBJC_VERSION_STRING @"0.13.0" -@interface GRPCHost () -// TODO(mlumish): Investigate whether caching channels with strong links is a good idea. -@property(nonatomic, strong) GRPCChannel *channel; -@end - -@implementation GRPCHost - -+ (instancetype)hostWithAddress:(NSString *)address { - return [[self alloc] initWithAddress:address]; +@implementation GRPCHost { + // TODO(mlumish): Investigate whether caching channels with strong links is a good idea. + GRPCChannel *_channel; } -- (instancetype)init { - return [self initWithAddress:nil]; ++ (nullable instancetype)hostWithAddress:(NSString *)address { + return [[self alloc] initWithAddress:address]; } // Default initializer. -- (instancetype)initWithAddress:(NSString *)address { +- (nullable instancetype)initWithAddress:(NSString *)address { if (!address) { return nil; } @@ -95,46 +92,45 @@ return self; } -- (grpc_call *)unmanagedCallWithPath:(NSString *)path completionQueue:(GRPCCompletionQueue *)queue { - if (!queue || !path || !self.channel) { - return NULL; +- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue { + GRPCChannel *channel; + // This is racing -[GRPCHost disconnect]. + @synchronized(self) { + if (!_channel) { + _channel = [self newChannel]; + } + channel = _channel; } - return grpc_channel_create_call(self.channel.unmanagedChannel, - NULL, GRPC_PROPAGATE_DEFAULTS, - queue.unmanagedQueue, - path.UTF8String, - self.hostName.UTF8String, - gpr_inf_future(GPR_CLOCK_REALTIME), NULL); + return [channel unmanagedCallWithPath:path completionQueue:queue]; } -- (GRPCChannel *)channel { - // Create it lazily, because we don't want to open a connection just because someone is - // configuring a host. +- (NSDictionary *)channelArgs { + NSMutableDictionary *args = [NSMutableDictionary dictionary]; - if (!_channel) { - NSMutableDictionary *args = [NSMutableDictionary dictionary]; + // TODO(jcanizales): Add OS and device information (see + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ). + NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; + if (_userAgentPrefix) { + userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; + } + args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; - // TODO(jcanizales): Add OS and device information (see - // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ). - NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; - if (_userAgentPrefix) { - userAgent = [@[_userAgentPrefix, userAgent] componentsJoinedByString:@" "]; - } - args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; - - if (_secure) { - if (_hostNameOverride) { - args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride; - } - - _channel = [GRPCChannel secureChannelWithHost:_address - pathToCertificates:_pathToCertificates - channelArgs:args]; - } else { - _channel = [GRPCChannel insecureChannelWithHost:_address channelArgs:args]; - } + if (_secure && _hostNameOverride) { + args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride; + } + return args; +} + +- (GRPCChannel *)newChannel { + NSDictionary *args = [self channelArgs]; + if (_secure) { + return [GRPCChannel secureChannelWithHost:_address + pathToCertificates:_pathToCertificates + channelArgs:args]; + } else { + return [GRPCChannel insecureChannelWithHost:_address channelArgs:args]; } - return _channel; } - (NSString *)hostName { @@ -142,7 +138,16 @@ return _hostNameOverride ?: _address; } +- (void)disconnect { + // This is racing -[GRPCHost unmanagedCallWithPath:completionQueue:]. + @synchronized(self) { + _channel = nil; + } +} + // TODO(jcanizales): Don't let set |secure| to |NO| if |pathToCertificates| or |hostNameOverride| // have been set. Don't let set either of the latter if |secure| has been set to |NO|. @end + +NS_ASSUME_NONNULL_END diff --git a/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h b/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h new file mode 100644 index 0000000000..02871d5d02 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h @@ -0,0 +1,65 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * "X-macro" file that lists the flags names of Apple's Network Reachability API, along with a nice + * Objective-C method name used to query each of them. + * + * Example usage: To generate a dictionary from flag value to name, one can do: + + NSDictionary *flagNames = @{ +#define GRPC_XMACRO_ITEM(methodName, FlagName) \ + @(kSCNetworkReachabilityFlags ## FlagName): @#methodName, +#include "GRXReachabilityFlagNames.xmacro.h" +#undef GRPC_XMACRO_ITEM + }; + + XCTAssertEqualObjects(flagNames[@(kSCNetworkReachabilityFlagsIsWWAN)], @"isCell"); + + */ + +#ifndef GRPC_XMACRO_ITEM +#error This file is to be used with the "X-macro" pattern: Please #define \ + GRPC_XMACRO_ITEM(methodName, FlagName), then #include this file, and then #undef \ + GRPC_XMACRO_ITEM. +#endif + +GRPC_XMACRO_ITEM(isCell, IsWWAN) +GRPC_XMACRO_ITEM(reachable, Reachable) +GRPC_XMACRO_ITEM(transientConnection, TransientConnection) +GRPC_XMACRO_ITEM(connectionRequired, ConnectionRequired) +GRPC_XMACRO_ITEM(connectionOnTraffic, ConnectionOnTraffic) +GRPC_XMACRO_ITEM(interventionRequired, InterventionRequired) +GRPC_XMACRO_ITEM(connectionOnDemand, ConnectionOnDemand) +GRPC_XMACRO_ITEM(isLocalAddress, IsLocalAddress) +GRPC_XMACRO_ITEM(isDirect, IsDirect) diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index 71e7e0e54e..e37ed1b59f 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -34,7 +34,6 @@ #import <Foundation/Foundation.h> #include <grpc/grpc.h> -#import "GRPCChannel.h" #import "GRPCRequestHeaders.h" @interface GRPCOperation : NSObject @@ -94,4 +93,5 @@ - (void)startBatchWithOperations:(NSArray *)ops; - (void)cancel; + @end |