Refactor channel pool

pull/16190/head
Muxi Yan 6 years ago
parent d72d5b2c8e
commit 37dbad80d5
  1. 4
      src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
  2. 69
      src/objective-c/GRPCClient/private/GRPCChannel.h
  3. 397
      src/objective-c/GRPCClient/private/GRPCChannel.m
  4. 55
      src/objective-c/GRPCClient/private/GRPCChannelPool.h
  5. 215
      src/objective-c/GRPCClient/private/GRPCChannelPool.m
  6. 24
      src/objective-c/GRPCClient/private/GRPCWrappedCall.m
  7. 161
      src/objective-c/tests/ChannelTests/ChannelPoolTest.m
  8. 97
      src/objective-c/tests/ChannelTests/ChannelTests.m

@ -18,7 +18,7 @@
#import "GRPCCall+ChannelArg.h" #import "GRPCCall+ChannelArg.h"
#import "private/GRPCChannel.h" #import "private/GRPCChannelPool.h"
#import "private/GRPCHost.h" #import "private/GRPCHost.h"
#import <grpc/impl/codegen/compression_types.h> #import <grpc/impl/codegen/compression_types.h>
@ -36,7 +36,7 @@
} }
+ (void)closeOpenConnections { + (void)closeOpenConnections {
[GRPCChannel closeOpenConnections]; [GRPCChannelPool closeOpenConnections];
} }
+ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host { + (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host {

@ -20,11 +20,37 @@
#include <grpc/grpc.h> #include <grpc/grpc.h>
@protocol GRPCChannelFactory;
@class GRPCCompletionQueue; @class GRPCCompletionQueue;
@class GRPCCallOptions; @class GRPCCallOptions;
@class GRPCChannelConfiguration; @class GRPCChannelConfiguration;
struct grpc_channel_credentials; struct grpc_channel_credentials;
NS_ASSUME_NONNULL_BEGIN
/** Caching signature of a channel. */
@interface GRPCChannelConfiguration : NSObject<NSCopying>
/** The host that this channel is connected to. */
@property(copy, readonly) NSString *host;
/**
* Options of the corresponding call. Note that only the channel-related options are of interest to
* this class.
*/
@property(strong, readonly) GRPCCallOptions *callOptions;
/** Acquire the factory to generate a new channel with current configurations. */
@property(readonly) id<GRPCChannelFactory> channelFactory;
/** Acquire the dictionary of channel args with current configurations. */
@property(copy, readonly) NSDictionary *channelArgs;
- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions;
@end
/** /**
* Each separate instance of this class represents at least one TCP connection to the provided host. * Each separate instance of this class represents at least one TCP connection to the provided host.
*/ */
@ -35,40 +61,45 @@ struct grpc_channel_credentials;
+ (nullable instancetype) new NS_UNAVAILABLE; + (nullable instancetype) new NS_UNAVAILABLE;
/** /**
* Returns a channel connecting to \a host with options as \a callOptions. The channel may be new * Create a channel with remote \a host and signature \a channelConfigurations. Destroy delay is
* or a cached channel that is already connected. * defaulted to 30 seconds.
*/ */
+ (nullable instancetype)channelWithHost:(nonnull NSString *)host - (nullable instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration;
callOptions:(nullable GRPCCallOptions *)callOptions;
/** /**
* Create a channel object with the signature \a config. * Create a channel with remote \a host, signature \a channelConfigurations, and destroy delay of
* \a destroyDelay.
*/ */
+ (nullable instancetype)createChannelWithConfiguration:(nonnull GRPCChannelConfiguration *)config; - (nullable instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration
destroyDelay:(NSTimeInterval)destroyDelay NS_DESIGNATED_INITIALIZER;
/** /**
* Get a grpc core call object from this channel. * Create a grpc core call object from this channel. The channel's refcount is added by 1. If no
* call is created, NULL is returned, and if the reason is because the channel is already
* disconnected, \a disconnected is set to YES. When the returned call is unreffed, the caller is
* obligated to call \a unref method once. \a disconnected may be null.
*/ */
- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path - (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
completionQueue:(nonnull GRPCCompletionQueue *)queue completionQueue:(GRPCCompletionQueue *)queue
callOptions:(nonnull GRPCCallOptions *)callOptions; callOptions:(GRPCCallOptions *)callOptions
disconnected:(BOOL * _Nullable)disconnected;
/** /**
* Increase the refcount of the channel. If the channel was timed to be destroyed, cancel the timer. * Unref the channel when a call is done. It also decreases the channel's refcount. If the refcount
* of the channel decreases to 0, the channel is destroyed after the destroy delay.
*/ */
- (void)ref; - (void)unref;
/** /**
* Decrease the refcount of the channel. If the refcount of the channel decrease to 0, the channel * Force the channel to be disconnected and destroyed.
* is destroyed after 30 seconds.
*/ */
- (void)unref; - (void)disconnect;
/** /**
* Force the channel to be disconnected and destroyed immediately. * Return whether the channel is already disconnected.
*/ */
- (void)disconnect; @property(readonly) BOOL disconnected;
// TODO (mxyan): deprecate with GRPCCall:closeOpenConnections
+ (void)closeOpenConnections;
@end @end
NS_ASSUME_NONNULL_END

@ -28,141 +28,222 @@
#import "GRPCInsecureChannelFactory.h" #import "GRPCInsecureChannelFactory.h"
#import "GRPCSecureChannelFactory.h" #import "GRPCSecureChannelFactory.h"
#import "version.h" #import "version.h"
#import "../internal/GRPCCallOptions+Internal.h"
#import <GRPCClient/GRPCCall+Cronet.h> #import <GRPCClient/GRPCCall+Cronet.h>
#import <GRPCClient/GRPCCallOptions.h> #import <GRPCClient/GRPCCallOptions.h>
/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */ /** When all calls of a channel are destroyed, destroy the channel after this much seconds. */
NSTimeInterval kChannelDestroyDelay = 30; NSTimeInterval kDefaultChannelDestroyDelay = 30;
/** Global instance of channel pool. */ @implementation GRPCChannelConfiguration
static GRPCChannelPool *gChannelPool;
/** - (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
* Time the channel destroy when the channel's calls are unreffed. If there's new call, reset the NSAssert(host.length, @"Host must not be empty.");
* timer. NSAssert(callOptions, @"callOptions must not be empty.");
*/ if ((self = [super init])) {
@interface GRPCChannelRef : NSObject _host = [host copy];
_callOptions = [callOptions copy];
}
return self;
}
- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay - (id<GRPCChannelFactory>)channelFactory {
destroyChannelCallback:(void (^)())destroyChannelCallback; NSError *error;
id<GRPCChannelFactory> factory;
GRPCTransportType type = _callOptions.transportType;
switch (type) {
case GRPCTransportTypeChttp2BoringSSL:
// TODO (mxyan): Remove when the API is deprecated
#ifdef GRPC_COMPILE_WITH_CRONET
if (![GRPCCall isUsingCronet]) {
#endif
factory = [GRPCSecureChannelFactory
factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
privateKey:_callOptions.PEMPrivateKey
certChain:_callOptions.PEMCertChain
error:&error];
if (factory == nil) {
NSLog(@"Error creating secure channel factory: %@", error);
}
return factory;
#ifdef GRPC_COMPILE_WITH_CRONET
}
#endif
// fallthrough
case GRPCTransportTypeCronet:
return [GRPCCronetChannelFactory sharedInstance];
case GRPCTransportTypeInsecure:
return [GRPCInsecureChannelFactory sharedInstance];
}
}
/** Add call ref count to the channel and maybe reset the timer. */ - (NSDictionary *)channelArgs {
- (void)refChannel; NSMutableDictionary *args = [NSMutableDictionary new];
/** Reduce call ref count to the channel and maybe set the timer. */ NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
- (void)unrefChannel; NSString *userAgentPrefix = _callOptions.userAgentPrefix;
if (userAgentPrefix) {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] =
[_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
} else {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
}
/** Disconnect the channel. Any further ref/unref are discarded. */ NSString *hostNameOverride = _callOptions.hostNameOverride;
- (void)disconnect; if (hostNameOverride) {
args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride;
}
@end if (_callOptions.responseSizeLimit) {
args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] =
[NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit];
}
@implementation GRPCChannelRef { if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) {
NSTimeInterval _destroyDelay; args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
void (^_destroyChannelCallback)(); [NSNumber numberWithInt:_callOptions.compressionAlgorithm];
}
NSUInteger _refCount; if (_callOptions.keepaliveInterval != 0) {
BOOL _disconnected; args[@GRPC_ARG_KEEPALIVE_TIME_MS] =
dispatch_queue_t _dispatchQueue; [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)];
args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)];
}
/** if (_callOptions.retryEnabled == NO) {
* Date and time when last timer is scheduled. If a firing timer's scheduled date is different args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled];
* from this, it is discarded. }
*/
NSDate *_lastDispatch;
}
- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay if (_callOptions.connectMinTimeout > 0) {
destroyChannelCallback:(void (^)())destroyChannelCallback { args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] =
if ((self = [super init])) { [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMinTimeout * 1000)];
_destroyDelay = destroyDelay; }
_destroyChannelCallback = destroyChannelCallback; if (_callOptions.connectInitialBackoff > 0) {
args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber
numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectInitialBackoff * 1000)];
}
if (_callOptions.connectMaxBackoff > 0) {
args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMaxBackoff * 1000)];
}
_refCount = 1; if (_callOptions.logContext != nil) {
_disconnected = NO; args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext;
if (@available(iOS 8.0, *)) {
_dispatchQueue = dispatch_queue_create(
NULL,
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1));
} else {
_dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
_lastDispatch = nil;
} }
return self;
}
- (void)refChannel { if (_callOptions.channelPoolDomain.length != 0) {
dispatch_async(_dispatchQueue, ^{ args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain;
if (!self->_disconnected) { }
self->_refCount++;
self->_lastDispatch = nil; [args addEntriesFromDictionary:_callOptions.additionalChannelArgs];
}
}); return args;
} }
- (void)unrefChannel { - (nonnull id)copyWithZone:(nullable NSZone *)zone {
dispatch_async(_dispatchQueue, ^{ GRPCChannelConfiguration *newConfig =
if (!self->_disconnected) { [[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions];
self->_refCount--;
if (self->_refCount == 0) { return newConfig;
NSDate *now = [NSDate date];
self->_lastDispatch = now;
dispatch_time_t delay =
dispatch_time(DISPATCH_TIME_NOW, (int64_t)self->_destroyDelay * NSEC_PER_SEC);
dispatch_after(delay, self->_dispatchQueue, ^{
[self timedDisconnectWithScheduleDate:now];
});
}
}
});
} }
- (void)disconnect { - (BOOL)isEqual:(id)object {
dispatch_async(_dispatchQueue, ^{ if (![object isKindOfClass:[GRPCChannelConfiguration class]]) {
if (!self->_disconnected) { return NO;
self->_lastDispatch = nil; }
self->_disconnected = YES; GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object;
// Break retain loop if (!(obj.host == _host || (_host != nil && [obj.host isEqualToString:_host]))) return NO;
self->_destroyChannelCallback = nil; if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions]))
} return NO;
});
return YES;
} }
- (void)timedDisconnectWithScheduleDate:(NSDate *)scheduleDate { - (NSUInteger)hash {
dispatch_async(_dispatchQueue, ^{ NSUInteger result = 0;
if (self->_disconnected || self->_lastDispatch != scheduleDate) { result ^= _host.hash;
return; result ^= _callOptions.channelOptionsHash;
}
self->_lastDispatch = nil; return result;
self->_disconnected = YES;
self->_destroyChannelCallback();
// Break retain loop
self->_destroyChannelCallback = nil;
});
} }
@end @end
@implementation GRPCChannel { @implementation GRPCChannel {
GRPCChannelConfiguration *_configuration; GRPCChannelConfiguration *_configuration;
grpc_channel *_unmanagedChannel;
GRPCChannelRef *_channelRef;
dispatch_queue_t _dispatchQueue; dispatch_queue_t _dispatchQueue;
grpc_channel *_unmanagedChannel;
NSTimeInterval _destroyDelay;
NSUInteger _refcount;
NSDate *_lastDispatch;
}
@synthesize disconnected = _disconnected;
- (nullable instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
return [self initWithChannelConfiguration:channelConfiguration
destroyDelay:kDefaultChannelDestroyDelay];
}
- (nullable instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration
destroyDelay:(NSTimeInterval)destroyDelay {
NSAssert(channelConfiguration, @"channelConfiguration must not be empty.");
NSAssert(destroyDelay > 0, @"destroyDelay must be greater than 0.");
if ((self = [super init])) {
_configuration = [channelConfiguration copy];
if (@available(iOS 8.0, *)) {
_dispatchQueue = dispatch_queue_create(
NULL,
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1));
} else {
_dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
// Create gRPC core channel object.
NSString *host = channelConfiguration.host;
NSAssert(host.length != 0, @"host cannot be nil");
NSDictionary *channelArgs;
if (channelConfiguration.callOptions.additionalChannelArgs.count != 0) {
NSMutableDictionary *args = [channelConfiguration.channelArgs mutableCopy];
[args addEntriesFromDictionary:channelConfiguration.callOptions.additionalChannelArgs];
channelArgs = args;
} else {
channelArgs = channelConfiguration.channelArgs;
}
id<GRPCChannelFactory> factory = channelConfiguration.channelFactory;
_unmanagedChannel = [factory createChannelWithHost:host channelArgs:channelArgs];
if (_unmanagedChannel == NULL) {
NSLog(@"Unable to create channel.");
return nil;
}
_destroyDelay = destroyDelay;
_disconnected = NO;
}
return self;
} }
- (grpc_call *)unmanagedCallWithPath:(NSString *)path - (grpc_call *)unmanagedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue completionQueue:(GRPCCompletionQueue *)queue
callOptions:(GRPCCallOptions *)callOptions { callOptions:(GRPCCallOptions *)callOptions
disconnected:(BOOL *)disconnected {
NSAssert(path.length, @"path must not be empty."); NSAssert(path.length, @"path must not be empty.");
NSAssert(queue, @"completionQueue must not be empty."); NSAssert(queue, @"completionQueue must not be empty.");
NSAssert(callOptions, @"callOptions must not be empty."); NSAssert(callOptions, @"callOptions must not be empty.");
__block grpc_call *call = nil; __block BOOL isDisconnected = NO;
__block grpc_call *call = NULL;
dispatch_sync(_dispatchQueue, ^{ dispatch_sync(_dispatchQueue, ^{
if (self->_unmanagedChannel) { if (self->_disconnected) {
isDisconnected = YES;
} else {
NSAssert(self->_unmanagedChannel != NULL, @"Invalid channel.");
NSString *serverAuthority = NSString *serverAuthority =
callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority; callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority;
NSTimeInterval timeout = callOptions.timeout; NSTimeInterval timeout = callOptions.timeout;
NSAssert(timeout >= 0, @"Invalid timeout"); NSAssert(timeout >= 0, @"Invalid timeout");
grpc_slice host_slice = grpc_empty_slice(); grpc_slice host_slice = grpc_empty_slice();
@ -171,10 +252,10 @@ static GRPCChannelPool *gChannelPool;
} }
grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String); grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
gpr_timespec deadline_ms = gpr_timespec deadline_ms =
timeout == 0 timeout == 0
? gpr_inf_future(GPR_CLOCK_REALTIME) ? gpr_inf_future(GPR_CLOCK_REALTIME)
: gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN)); gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
call = grpc_channel_create_call(self->_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS, call = grpc_channel_create_call(self->_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
queue.unmanagedQueue, path_slice, queue.unmanagedQueue, path_slice,
serverAuthority ? &host_slice : NULL, deadline_ms, NULL); serverAuthority ? &host_slice : NULL, deadline_ms, NULL);
@ -182,71 +263,64 @@ static GRPCChannelPool *gChannelPool;
grpc_slice_unref(host_slice); grpc_slice_unref(host_slice);
} }
grpc_slice_unref(path_slice); grpc_slice_unref(path_slice);
} else { if (call == NULL) {
NSAssert(self->_unmanagedChannel != nil, @"Invalid channeg."); NSLog(@"Unable to create call.");
} else {
// Ref the channel;
[self ref];
}
} }
}); });
if (disconnected != nil) {
*disconnected = isDisconnected;
}
return call; return call;
} }
// This function should be called on _dispatchQueue.
- (void)ref { - (void)ref {
dispatch_async(_dispatchQueue, ^{ _refcount++;
if (self->_unmanagedChannel) { if (_refcount == 1 && _lastDispatch != nil) {
[self->_channelRef refChannel]; _lastDispatch = nil;
} }
});
} }
- (void)unref { - (void)unref {
dispatch_async(_dispatchQueue, ^{ dispatch_async(_dispatchQueue, ^{
if (self->_unmanagedChannel) { self->_refcount--;
[self->_channelRef unrefChannel]; if (self->_refcount == 0 && !self->_disconnected) {
// Start timer.
dispatch_time_t delay =
dispatch_time(DISPATCH_TIME_NOW, (int64_t)self->_destroyDelay * NSEC_PER_SEC);
NSDate *now = [NSDate date];
self->_lastDispatch = now;
dispatch_after(delay, self->_dispatchQueue, ^{
if (self->_lastDispatch == now) {
grpc_channel_destroy(self->_unmanagedChannel);
self->_unmanagedChannel = NULL;
self->_disconnected = YES;
}
});
} }
}); });
} }
- (void)disconnect { - (void)disconnect {
dispatch_async(_dispatchQueue, ^{ dispatch_async(_dispatchQueue, ^{
if (self->_unmanagedChannel) { if (!self->_disconnected) {
grpc_channel_destroy(self->_unmanagedChannel); grpc_channel_destroy(self->_unmanagedChannel);
self->_unmanagedChannel = nil; self->_unmanagedChannel = nil;
[self->_channelRef disconnect]; self->_disconnected = YES;
} }
}); });
} }
- (void)destroyChannel { - (BOOL)disconnected {
dispatch_async(_dispatchQueue, ^{ __block BOOL disconnected;
if (self->_unmanagedChannel) { dispatch_sync(_dispatchQueue, ^{
grpc_channel_destroy(self->_unmanagedChannel); disconnected = self->_disconnected;
self->_unmanagedChannel = nil;
[gChannelPool removeChannel:self];
}
}); });
} return disconnected;
- (nullable instancetype)initWithUnmanagedChannel:(grpc_channel *_Nullable)unmanagedChannel
configuration:(GRPCChannelConfiguration *)configuration {
NSAssert(configuration, @"Configuration must not be empty.");
if (!unmanagedChannel) {
return nil;
}
if ((self = [super init])) {
_unmanagedChannel = unmanagedChannel;
_configuration = [configuration copy];
_channelRef = [[GRPCChannelRef alloc] initWithDestroyDelay:kChannelDestroyDelay
destroyChannelCallback:^{
[self destroyChannel];
}];
if (@available(iOS 8.0, *)) {
_dispatchQueue = dispatch_queue_create(
NULL,
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1));
} else {
_dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
}
return self;
} }
- (void)dealloc { - (void)dealloc {
@ -255,47 +329,4 @@ static GRPCChannelPool *gChannelPool;
} }
} }
+ (nullable instancetype)createChannelWithConfiguration:(GRPCChannelConfiguration *)config {
NSAssert(config != nil, @"configuration cannot be empty");
NSString *host = config.host;
NSAssert(host.length != 0, @"host cannot be nil");
NSDictionary *channelArgs;
if (config.callOptions.additionalChannelArgs.count != 0) {
NSMutableDictionary *args = [config.channelArgs mutableCopy];
[args addEntriesFromDictionary:config.callOptions.additionalChannelArgs];
channelArgs = args;
} else {
channelArgs = config.channelArgs;
}
id<GRPCChannelFactory> factory = config.channelFactory;
grpc_channel *unmanaged_channel = [factory createChannelWithHost:host channelArgs:channelArgs];
return [[GRPCChannel alloc] initWithUnmanagedChannel:unmanaged_channel configuration:config];
}
+ (nullable instancetype)channelWithHost:(NSString *)host
callOptions:(GRPCCallOptions *)callOptions {
static dispatch_once_t initChannelPool;
dispatch_once(&initChannelPool, ^{
gChannelPool = [[GRPCChannelPool alloc] init];
});
NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];
if (hostURL.host && !hostURL.port) {
host = [hostURL.host stringByAppendingString:@":443"];
}
GRPCChannelConfiguration *channelConfig =
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
if (channelConfig == nil) {
return nil;
}
return [gChannelPool channelWithConfiguration:channelConfig];
}
+ (void)closeOpenConnections {
[gChannelPool removeAndCloseAllChannels];
}
@end @end

@ -29,48 +29,45 @@ NS_ASSUME_NONNULL_BEGIN
@class GRPCChannel; @class GRPCChannel;
/** Caching signature of a channel. */
@interface GRPCChannelConfiguration : NSObject<NSCopying>
/** The host that this channel is connected to. */
@property(copy, readonly) NSString *host;
/**
* Options of the corresponding call. Note that only the channel-related options are of interest to
* this class.
*/
@property(strong, readonly) GRPCCallOptions *callOptions;
/** Acquire the factory to generate a new channel with current configurations. */
@property(readonly) id<GRPCChannelFactory> channelFactory;
/** Acquire the dictionary of channel args with current configurations. */
@property(copy, readonly) NSDictionary *channelArgs;
- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions;
@end
/** /**
* Manage the pool of connected channels. When a channel is no longer referenced by any call, * Manage the pool of connected channels. When a channel is no longer referenced by any call,
* destroy the channel after a certain period of time elapsed. * destroy the channel after a certain period of time elapsed.
*/ */
@interface GRPCChannelPool : NSObject @interface GRPCChannelPool : NSObject
/**
* Get the singleton instance
*/
+ (nullable instancetype)sharedInstance;
/** /**
* Return a channel with a particular configuration. If the channel does not exist, execute \a * Return a channel with a particular configuration. If the channel does not exist, execute \a
* createChannel then add it in the pool. If the channel exists, increase its reference count. * createChannel then add it in the pool. If the channel exists, increase its reference count.
*/ */
- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration; - (GRPCChannel *)channelWithHost:(NSString *)host
callOptions:(GRPCCallOptions *)callOptions;
/**
* This method is deprecated.
*
* Destroy all open channels and close their connections.
*/
+ (void)closeOpenConnections;
/** Remove a channel from the pool. */ // Test-only methods below
- (void)removeChannel:(GRPCChannel *)channel;
/** Clear all channels in the pool. */ /**
- (void)removeAllChannels; * Return a channel with a special destroy delay. If \a destroyDelay is 0, use the default destroy
* delay.
*/
- (GRPCChannel *)channelWithHost:(NSString *)host
callOptions:(GRPCCallOptions *)callOptions
destroyDelay:(NSTimeInterval)destroyDelay;
/** Clear all channels in the pool and destroy the channels. */ /**
- (void)removeAndCloseAllChannels; * Simulate a network transition event and destroy all channels.
*/
- (void)destroyAllChannels;
@end @end

@ -33,147 +33,23 @@
extern const char *kCFStreamVarName; extern const char *kCFStreamVarName;
@implementation GRPCChannelConfiguration static GRPCChannelPool *gChannelPool;
static dispatch_once_t gInitChannelPool;
- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
NSAssert(host.length, @"Host must not be empty.");
NSAssert(callOptions, @"callOptions must not be empty.");
if ((self = [super init])) {
_host = [host copy];
_callOptions = [callOptions copy];
}
return self;
}
- (id<GRPCChannelFactory>)channelFactory {
NSError *error;
id<GRPCChannelFactory> factory;
GRPCTransportType type = _callOptions.transportType;
switch (type) {
case GRPCTransportTypeChttp2BoringSSL:
// TODO (mxyan): Remove when the API is deprecated
#ifdef GRPC_COMPILE_WITH_CRONET
if (![GRPCCall isUsingCronet]) {
#endif
factory = [GRPCSecureChannelFactory
factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
privateKey:_callOptions.PEMPrivateKey
certChain:_callOptions.PEMCertChain
error:&error];
if (factory == nil) {
NSLog(@"Error creating secure channel factory: %@", error);
}
return factory;
#ifdef GRPC_COMPILE_WITH_CRONET
}
#endif
// fallthrough
case GRPCTransportTypeCronet:
return [GRPCCronetChannelFactory sharedInstance];
case GRPCTransportTypeInsecure:
return [GRPCInsecureChannelFactory sharedInstance];
}
}
- (NSDictionary *)channelArgs {
NSMutableDictionary *args = [NSMutableDictionary new];
NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
NSString *userAgentPrefix = _callOptions.userAgentPrefix;
if (userAgentPrefix) {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] =
[_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
} else {
args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
}
NSString *hostNameOverride = _callOptions.hostNameOverride;
if (hostNameOverride) {
args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride;
}
if (_callOptions.responseSizeLimit) {
args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] =
[NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit];
}
if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) {
args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
[NSNumber numberWithInt:_callOptions.compressionAlgorithm];
}
if (_callOptions.keepaliveInterval != 0) {
args[@GRPC_ARG_KEEPALIVE_TIME_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)];
args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)];
}
if (_callOptions.retryEnabled == NO) {
args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled];
}
if (_callOptions.connectMinTimeout > 0) {
args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMinTimeout * 1000)];
}
if (_callOptions.connectInitialBackoff > 0) {
args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber
numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectInitialBackoff * 1000)];
}
if (_callOptions.connectMaxBackoff > 0) {
args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] =
[NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMaxBackoff * 1000)];
}
if (_callOptions.logContext != nil) {
args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext;
}
if (_callOptions.channelPoolDomain.length != 0) {
args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain;
}
[args addEntriesFromDictionary:_callOptions.additionalChannelArgs];
return args;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
GRPCChannelConfiguration *newConfig =
[[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions];
return newConfig;
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[GRPCChannelConfiguration class]]) {
return NO;
}
GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object;
if (!(obj.host == _host || (_host != nil && [obj.host isEqualToString:_host]))) return NO;
if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions]))
return NO;
return YES;
}
- (NSUInteger)hash {
NSUInteger result = 0;
result ^= _host.hash;
result ^= _callOptions.channelOptionsHash;
return result;
}
@end
#pragma mark GRPCChannelPool
@implementation GRPCChannelPool { @implementation GRPCChannelPool {
NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannel *> *_channelPool; NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannel *> *_channelPool;
} }
+ (nullable instancetype)sharedInstance {
dispatch_once(&gInitChannelPool, ^{
gChannelPool = [[GRPCChannelPool alloc] init];
if (gChannelPool == nil) {
[NSException raise:NSMallocException format:@"Cannot initialize global channel pool."];
}
});
return gChannelPool;
}
- (instancetype)init { - (instancetype)init {
if ((self = [super init])) { if ((self = [super init])) {
_channelPool = [NSMutableDictionary dictionary]; _channelPool = [NSMutableDictionary dictionary];
@ -187,61 +63,56 @@ extern const char *kCFStreamVarName;
return self; return self;
} }
- (void)dealloc { - (GRPCChannel *)channelWithHost:(NSString *)host
[GRPCConnectivityMonitor unregisterObserver:self]; callOptions:(GRPCCallOptions *)callOptions {
return [self channelWithHost:host
callOptions:callOptions
destroyDelay:0];
} }
- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration { - (GRPCChannel *)channelWithHost:(NSString *)host
NSAssert(configuration != nil, @"Must has a configuration"); callOptions:(GRPCCallOptions *)callOptions
destroyDelay:(NSTimeInterval)destroyDelay {
NSAssert(host.length > 0, @"Host must not be empty.");
NSAssert(callOptions != nil, @"callOptions must not be empty.");
GRPCChannel *channel; GRPCChannel *channel;
GRPCChannelConfiguration *configuration =
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
@synchronized(self) { @synchronized(self) {
if ([_channelPool objectForKey:configuration]) { channel = _channelPool[configuration];
channel = _channelPool[configuration]; if (channel == nil || channel.disconnected) {
[channel ref]; if (destroyDelay == 0) {
} else { channel = [[GRPCChannel alloc] initWithChannelConfiguration:configuration];
channel = [GRPCChannel createChannelWithConfiguration:configuration]; } else {
if (channel != nil) { channel = [[GRPCChannel alloc] initWithChannelConfiguration:configuration destroyDelay:destroyDelay];
_channelPool[configuration] = channel;
} }
_channelPool[configuration] = channel;
} }
} }
return channel; return channel;
} }
- (void)removeChannel:(GRPCChannel *)channel {
@synchronized(self) {
__block GRPCChannelConfiguration *keyToDelete = nil;
[_channelPool
enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key,
GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) {
if (obj == channel) {
keyToDelete = key;
*stop = YES;
}
}];
[self->_channelPool removeObjectForKey:keyToDelete];
}
}
- (void)removeAllChannels {
@synchronized(self) { + (void)closeOpenConnections {
_channelPool = [NSMutableDictionary dictionary]; [[GRPCChannelPool sharedInstance] destroyAllChannels];
}
} }
- (void)removeAndCloseAllChannels { - (void)destroyAllChannels {
@synchronized(self) { @synchronized(self) {
[_channelPool for (id key in _channelPool) {
enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key, [_channelPool[key] disconnect];
GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) { }
[obj disconnect];
}];
_channelPool = [NSMutableDictionary dictionary]; _channelPool = [NSMutableDictionary dictionary];
} }
} }
- (void)connectivityChange:(NSNotification *)note { - (void)connectivityChange:(NSNotification *)note {
[self removeAndCloseAllChannels]; [self destroyAllChannels];
}
- (void)dealloc {
[GRPCConnectivityMonitor unregisterObserver:self];
} }
@end @end

@ -24,6 +24,7 @@
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
#import "GRPCChannel.h" #import "GRPCChannel.h"
#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h" #import "GRPCCompletionQueue.h"
#import "GRPCHost.h" #import "GRPCHost.h"
#import "NSData+GRPC.h" #import "NSData+GRPC.h"
@ -256,13 +257,21 @@
// consuming too many threads and having contention of multiple calls in a single completion // consuming too many threads and having contention of multiple calls in a single completion
// queue. Currently we use a singleton queue. // queue. Currently we use a singleton queue.
_queue = [GRPCCompletionQueue completionQueue]; _queue = [GRPCCompletionQueue completionQueue];
_channel = [GRPCChannel channelWithHost:host callOptions:callOptions]; BOOL disconnected;
if (_channel == nil) { do {
NSLog(@"Failed to get a channel for the host."); _channel = [[GRPCChannelPool sharedInstance] channelWithHost:host callOptions:callOptions];
return nil; if (_channel == nil) {
} NSLog(@"Failed to get a channel for the host.");
_call = [_channel unmanagedCallWithPath:path completionQueue:_queue callOptions:callOptions]; return nil;
if (_call == NULL) { }
_call = [_channel unmanagedCallWithPath:path
completionQueue:_queue
callOptions:callOptions
disconnected:&disconnected];
// Try create another channel if the current channel is disconnected (due to idleness or
// connectivity monitor disconnection).
} while (_call == NULL && disconnected);
if (_call == nil) {
NSLog(@"Failed to create a call."); NSLog(@"Failed to create a call.");
return nil; return nil;
} }
@ -317,6 +326,7 @@
- (void)dealloc { - (void)dealloc {
if (_call) { if (_call) {
grpc_call_unref(_call); grpc_call_unref(_call);
[_channel unref];
} }
[_channel unref]; [_channel unref];
_channel = nil; _channel = nil;

@ -20,6 +20,7 @@
#import "../../GRPCClient/private/GRPCChannel.h" #import "../../GRPCClient/private/GRPCChannel.h"
#import "../../GRPCClient/private/GRPCChannelPool.h" #import "../../GRPCClient/private/GRPCChannelPool.h"
#import "../../GRPCClient/private/GRPCCompletionQueue.h"
#define TEST_TIMEOUT 32 #define TEST_TIMEOUT 32
@ -35,92 +36,104 @@ NSString *kDummyHost = @"dummy.host";
grpc_init(); grpc_init();
} }
- (void)testCreateChannel { - (void)testChannelPooling {
NSString *kDummyHost = @"dummy.host"; NSString *kDummyHost = @"dummy.host";
NSString *kDummyHost2 = @"dummy.host2";
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init];
options1.transportType = GRPCTransportTypeInsecure;
GRPCCallOptions *options2 = [options1 copy]; GRPCCallOptions *options2 = [options1 copy];
GRPCChannelConfiguration *config1 = GRPCMutableCallOptions *options3 = [options2 mutableCopy];
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; options3.transportType = GRPCTransportTypeInsecure;
GRPCChannelConfiguration *config2 =
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; GRPCChannelPool *pool = [GRPCChannelPool sharedInstance];
GRPCChannelPool *pool = [[GRPCChannelPool alloc] init];
GRPCChannel *channel1 = [pool channelWithHost:kDummyHost
GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; callOptions:options1];
GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; GRPCChannel *channel2 = [pool channelWithHost:kDummyHost
callOptions:options2];
GRPCChannel *channel3 = [pool channelWithHost:kDummyHost2
callOptions:options1];
GRPCChannel *channel4 = [pool channelWithHost:kDummyHost
callOptions:options3];
XCTAssertEqual(channel1, channel2); XCTAssertEqual(channel1, channel2);
XCTAssertNotEqual(channel1, channel3);
XCTAssertNotEqual(channel1, channel4);
XCTAssertNotEqual(channel3, channel4);
} }
- (void)testChannelRemove { - (void)testDestroyAllChannels {
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; NSString *kDummyHost = @"dummy.host";
options1.transportType = GRPCTransportTypeInsecure;
GRPCChannelConfiguration *config1 =
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1];
GRPCChannelPool *pool = [[GRPCChannelPool alloc] init];
GRPCChannel *channel1 = [pool channelWithConfiguration:config1];
[pool removeChannel:channel1];
GRPCChannel *channel2 = [pool channelWithConfiguration:config1];
XCTAssertNotEqual(channel1, channel2);
}
extern NSTimeInterval kChannelDestroyDelay;
- (void)testChannelTimeoutCancel { GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
NSTimeInterval kOriginalInterval = kChannelDestroyDelay; GRPCChannelPool *pool = [GRPCChannelPool sharedInstance];
kChannelDestroyDelay = 3.0; GRPCChannel *channel = [pool channelWithHost:kDummyHost
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; callOptions:options];
options1.transportType = GRPCTransportTypeInsecure; grpc_call *call = [channel unmanagedCallWithPath:@"dummy.path"
GRPCChannelConfiguration *config1 = completionQueue:[GRPCCompletionQueue completionQueue]
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; callOptions:options
GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; disconnected:nil];
GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; [pool destroyAllChannels];
[channel1 unref]; XCTAssertTrue(channel.disconnected);
sleep(1); GRPCChannel *channel2 = [pool channelWithHost:kDummyHost
GRPCChannel *channel2 = [pool channelWithConfiguration:config1]; callOptions:options];
XCTAssertEqual(channel1, channel2); XCTAssertNotEqual(channel, channel2);
sleep((int)kChannelDestroyDelay + 2); grpc_call_unref(call);
GRPCChannel *channel3 = [pool channelWithConfiguration:config1];
XCTAssertEqual(channel1, channel3);
kChannelDestroyDelay = kOriginalInterval;
} }
- (void)testChannelDisconnect { - (void)testGetChannelBeforeChannelTimedDisconnection {
NSString *kDummyHost = @"dummy.host"; NSString *kDummyHost = @"dummy.host";
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; const NSTimeInterval kDestroyDelay = 1;
options1.transportType = GRPCTransportTypeInsecure;
GRPCCallOptions *options2 = [options1 copy]; GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
GRPCChannelConfiguration *config1 = GRPCChannelPool *pool = [GRPCChannelPool sharedInstance];
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; GRPCChannel *channel = [pool channelWithHost:kDummyHost
GRPCChannelConfiguration *config2 = callOptions:options
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; destroyDelay:kDestroyDelay];
GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; grpc_call *call = [channel unmanagedCallWithPath:@"dummy.path"
completionQueue:[GRPCCompletionQueue completionQueue]
GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; callOptions:options
[pool removeAndCloseAllChannels]; disconnected:nil];
GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; grpc_call_unref(call);
XCTAssertNotEqual(channel1, channel2); [channel unref];
// Test that we can still get the channel at this time
GRPCChannel *channel2 = [pool channelWithHost:kDummyHost
callOptions:options
destroyDelay:kDestroyDelay];
XCTAssertEqual(channel, channel2);
call = [channel2 unmanagedCallWithPath:@"dummy.path"
completionQueue:[GRPCCompletionQueue completionQueue]
callOptions:options
disconnected:nil];
// Test that after the destroy delay, the channel is still alive
sleep(kDestroyDelay + 1);
XCTAssertFalse(channel.disconnected);
} }
- (void)testClearChannels { - (void)testGetChannelAfterChannelTimedDisconnection {
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; NSString *kDummyHost = @"dummy.host";
options1.transportType = GRPCTransportTypeInsecure; const NSTimeInterval kDestroyDelay = 1;
GRPCMutableCallOptions *options2 = [[GRPCMutableCallOptions alloc] init];
options2.transportType = GRPCTransportTypeChttp2BoringSSL; GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
GRPCChannelConfiguration *config1 = GRPCChannelPool *pool = [GRPCChannelPool sharedInstance];
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options1]; GRPCChannel *channel = [pool channelWithHost:kDummyHost
GRPCChannelConfiguration *config2 = callOptions:options
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options2]; destroyDelay:kDestroyDelay];
GRPCChannelPool *pool = [[GRPCChannelPool alloc] init]; grpc_call *call = [channel unmanagedCallWithPath:@"dummy.path"
completionQueue:[GRPCCompletionQueue completionQueue]
GRPCChannel *channel1 = [pool channelWithConfiguration:config1]; callOptions:options
GRPCChannel *channel2 = [pool channelWithConfiguration:config2]; disconnected:nil];
XCTAssertNotEqual(channel1, channel2); grpc_call_unref(call);
[channel unref];
[pool removeAndCloseAllChannels];
GRPCChannel *channel3 = [pool channelWithConfiguration:config1]; sleep(kDestroyDelay + 1);
GRPCChannel *channel4 = [pool channelWithConfiguration:config2];
XCTAssertNotEqual(channel1, channel3); // Test that we get new channel to the same host and with the same callOptions
XCTAssertNotEqual(channel2, channel4); GRPCChannel *channel2 = [pool channelWithHost:kDummyHost
callOptions:options
destroyDelay:kDestroyDelay];
XCTAssertNotEqual(channel, channel2);
} }
@end @end

@ -20,6 +20,7 @@
#import "../../GRPCClient/GRPCCallOptions.h" #import "../../GRPCClient/GRPCCallOptions.h"
#import "../../GRPCClient/private/GRPCChannel.h" #import "../../GRPCClient/private/GRPCChannel.h"
#import "../../GRPCClient/private/GRPCCompletionQueue.h"
@interface ChannelTests : XCTestCase @interface ChannelTests : XCTestCase
@ -31,63 +32,51 @@
grpc_init(); grpc_init();
} }
- (void)testSameConfiguration { - (void)testTimedDisconnection {
NSString *host = @"grpc-test.sandbox.googleapis.com"; NSString * const kHost = @"grpc-test.sandbox.googleapis.com";
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; const NSTimeInterval kDestroyDelay = 1;
options.userAgentPrefix = @"TestUAPrefix"; GRPCCallOptions *options = [[GRPCCallOptions alloc] init];
NSMutableDictionary *args = [NSMutableDictionary new]; GRPCChannelConfiguration *configuration = [[GRPCChannelConfiguration alloc] initWithHost:kHost callOptions:options];
args[@"abc"] = @"xyz"; GRPCChannel *channel = [[GRPCChannel alloc] initWithChannelConfiguration:configuration
options.additionalChannelArgs = [args copy]; destroyDelay:kDestroyDelay];
GRPCChannel *channel1 = [GRPCChannel channelWithHost:host callOptions:options]; BOOL disconnected;
GRPCChannel *channel2 = [GRPCChannel channelWithHost:host callOptions:options]; grpc_call *call = [channel unmanagedCallWithPath:@"dummy.path"
XCTAssertEqual(channel1, channel2); completionQueue:[GRPCCompletionQueue completionQueue]
GRPCMutableCallOptions *options2 = [options mutableCopy]; callOptions:options
options2.additionalChannelArgs = [args copy]; disconnected:&disconnected];
GRPCChannel *channel3 = [GRPCChannel channelWithHost:host callOptions:options2]; XCTAssertFalse(disconnected);
XCTAssertEqual(channel1, channel3); grpc_call_unref(call);
} [channel unref];
XCTAssertFalse(channel.disconnected, @"Channel is pre-maturely disconnected.");
sleep(kDestroyDelay + 1);
XCTAssertTrue(channel.disconnected, @"Channel is not disconnected after delay.");
- (void)testDifferentHost { // Check another call creation returns null and indicates disconnected.
NSString *host1 = @"grpc-test.sandbox.googleapis.com"; call = [channel unmanagedCallWithPath:@"dummy.path"
NSString *host2 = @"grpc-test2.sandbox.googleapis.com"; completionQueue:[GRPCCompletionQueue completionQueue]
NSString *host3 = @"http://grpc-test.sandbox.googleapis.com"; callOptions:options
NSString *host4 = @"dns://grpc-test.sandbox.googleapis.com"; disconnected:&disconnected];
NSString *host5 = @"grpc-test.sandbox.googleapis.com:80"; XCTAssert(call == NULL);
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; XCTAssertTrue(disconnected);
options.userAgentPrefix = @"TestUAPrefix";
NSMutableDictionary *args = [NSMutableDictionary new];
args[@"abc"] = @"xyz";
options.additionalChannelArgs = [args copy];
GRPCChannel *channel1 = [GRPCChannel channelWithHost:host1 callOptions:options];
GRPCChannel *channel2 = [GRPCChannel channelWithHost:host2 callOptions:options];
GRPCChannel *channel3 = [GRPCChannel channelWithHost:host3 callOptions:options];
GRPCChannel *channel4 = [GRPCChannel channelWithHost:host4 callOptions:options];
GRPCChannel *channel5 = [GRPCChannel channelWithHost:host5 callOptions:options];
XCTAssertNotEqual(channel1, channel2);
XCTAssertNotEqual(channel1, channel3);
XCTAssertNotEqual(channel1, channel4);
XCTAssertNotEqual(channel1, channel5);
} }
- (void)testDifferentChannelParameters { - (void)testForceDisconnection {
NSString *host = @"grpc-test.sandbox.googleapis.com"; NSString * const kHost = @"grpc-test.sandbox.googleapis.com";
GRPCMutableCallOptions *options1 = [[GRPCMutableCallOptions alloc] init]; const NSTimeInterval kDestroyDelay = 1;
options1.transportType = GRPCTransportTypeChttp2BoringSSL; GRPCCallOptions *options = [[GRPCCallOptions alloc] init];
NSMutableDictionary *args = [NSMutableDictionary new]; GRPCChannelConfiguration *configuration = [[GRPCChannelConfiguration alloc] initWithHost:kHost callOptions:options];
args[@"abc"] = @"xyz"; GRPCChannel *channel = [[GRPCChannel alloc] initWithChannelConfiguration:configuration
options1.additionalChannelArgs = [args copy]; destroyDelay:kDestroyDelay];
GRPCMutableCallOptions *options2 = [[GRPCMutableCallOptions alloc] init]; grpc_call *call = [channel unmanagedCallWithPath:@"dummy.path"
options2.transportType = GRPCTransportTypeInsecure; completionQueue:[GRPCCompletionQueue completionQueue]
options2.additionalChannelArgs = [args copy]; callOptions:options
GRPCMutableCallOptions *options3 = [[GRPCMutableCallOptions alloc] init]; disconnected:nil];
options3.transportType = GRPCTransportTypeChttp2BoringSSL; grpc_call_unref(call);
args[@"def"] = @"uvw"; [channel disconnect];
options3.additionalChannelArgs = [args copy]; XCTAssertTrue(channel.disconnected, @"Channel is not disconnected.");
GRPCChannel *channel1 = [GRPCChannel channelWithHost:host callOptions:options1];
GRPCChannel *channel2 = [GRPCChannel channelWithHost:host callOptions:options2]; // Test calling another unref here will not crash
GRPCChannel *channel3 = [GRPCChannel channelWithHost:host callOptions:options3]; [channel unref];
XCTAssertNotEqual(channel1, channel2);
XCTAssertNotEqual(channel1, channel3);
} }
@end @end

Loading…
Cancel
Save