|
@@ -28,6 +28,8 @@
|
|
#import "GRPCSecureChannelFactory.h"
|
|
#import "GRPCSecureChannelFactory.h"
|
|
#import "utilities.h"
|
|
#import "utilities.h"
|
|
#import "version.h"
|
|
#import "version.h"
|
|
|
|
+#import "GRPCWrappedCall.h"
|
|
|
|
+#import "GRPCCompletionQueue.h"
|
|
|
|
|
|
#import <GRPCClient/GRPCCall+Cronet.h>
|
|
#import <GRPCClient/GRPCCall+Cronet.h>
|
|
#include <grpc/support/log.h>
|
|
#include <grpc/support/log.h>
|
|
@@ -40,103 +42,139 @@ static dispatch_once_t gInitChannelPool;
|
|
/** 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. */
|
|
static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
|
|
static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
|
|
|
|
|
|
-@interface GRPCChannelPool ()
|
|
|
|
-
|
|
|
|
-- (GRPCChannel *)refChannelWithConfiguration:(GRPCChannelConfiguration *)configuration;
|
|
|
|
-
|
|
|
|
-- (void)unrefChannelWithConfiguration:(GRPCChannelConfiguration *)configuration;
|
|
|
|
-
|
|
|
|
-@end
|
|
|
|
-
|
|
|
|
@implementation GRPCPooledChannel {
|
|
@implementation GRPCPooledChannel {
|
|
- __weak GRPCChannelPool *_channelPool;
|
|
|
|
GRPCChannelConfiguration *_channelConfiguration;
|
|
GRPCChannelConfiguration *_channelConfiguration;
|
|
- NSMutableSet *_unmanagedCalls;
|
|
|
|
|
|
+ NSTimeInterval _destroyDelay;
|
|
|
|
+
|
|
|
|
+ NSHashTable<GRPCWrappedCall *> *_wrappedCalls;
|
|
GRPCChannel *_wrappedChannel;
|
|
GRPCChannel *_wrappedChannel;
|
|
|
|
+ NSDate *_lastTimedDestroy;
|
|
|
|
+ dispatch_queue_t _timerQueue;
|
|
}
|
|
}
|
|
|
|
|
|
-- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration
|
|
|
|
- channelPool:(GRPCChannelPool *)channelPool {
|
|
|
|
- NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty.");
|
|
|
|
- NSAssert(channelPool != nil, @"channelPool cannot be empty.");
|
|
|
|
- if (channelPool == nil || channelConfiguration == nil) {
|
|
|
|
- return nil;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ((self = [super init])) {
|
|
|
|
- _channelPool = channelPool;
|
|
|
|
- _channelConfiguration = channelConfiguration;
|
|
|
|
- _unmanagedCalls = [NSMutableSet set];
|
|
|
|
- _wrappedChannel = nil;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return self;
|
|
|
|
|
|
+- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
|
|
|
|
+ return [self initWithChannelConfiguration:channelConfiguration destroyDelay:kDefaultChannelDestroyDelay];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)dealloc {
|
|
- (void)dealloc {
|
|
- NSAssert([_unmanagedCalls count] == 0 && _wrappedChannel == nil,
|
|
|
|
- @"Pooled channel should only be"
|
|
|
|
- "destroyed after the wrapped channel is destroyed");
|
|
|
|
|
|
+ if ([_wrappedCalls objectEnumerator].allObjects.count != 0) {
|
|
|
|
+ NSEnumerator *enumerator = [_wrappedCalls objectEnumerator];
|
|
|
|
+ GRPCWrappedCall *wrappedCall;
|
|
|
|
+ while ((wrappedCall = [enumerator nextObject])) {
|
|
|
|
+ [wrappedCall channelDisconnected];
|
|
|
|
+ };
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-- (grpc_call *)unmanagedCallWithPath:(NSString *)path
|
|
|
|
- completionQueue:(GRPCCompletionQueue *)queue
|
|
|
|
- callOptions:(GRPCCallOptions *)callOptions {
|
|
|
|
|
|
+- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path
|
|
|
|
+completionQueue:(GRPCCompletionQueue *)queue
|
|
|
|
+callOptions:(GRPCCallOptions *)callOptions {
|
|
NSAssert(path.length > 0, @"path must not be empty.");
|
|
NSAssert(path.length > 0, @"path must not be empty.");
|
|
NSAssert(queue != nil, @"completionQueue must not be empty.");
|
|
NSAssert(queue != nil, @"completionQueue must not be empty.");
|
|
NSAssert(callOptions, @"callOptions must not be empty.");
|
|
NSAssert(callOptions, @"callOptions must not be empty.");
|
|
if (path.length == 0 || queue == nil || callOptions == nil) return NULL;
|
|
if (path.length == 0 || queue == nil || callOptions == nil) return NULL;
|
|
|
|
|
|
- grpc_call *call = NULL;
|
|
|
|
|
|
+ GRPCWrappedCall *call = nil;
|
|
|
|
+
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
if (_wrappedChannel == nil) {
|
|
if (_wrappedChannel == nil) {
|
|
- __strong GRPCChannelPool *strongPool = _channelPool;
|
|
|
|
- if (strongPool) {
|
|
|
|
- _wrappedChannel = [strongPool refChannelWithConfiguration:_channelConfiguration];
|
|
|
|
|
|
+ _wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration];
|
|
|
|
+ if (_wrappedChannel == nil) {
|
|
|
|
+ NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy.");
|
|
|
|
+ return nil;
|
|
}
|
|
}
|
|
- NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy.");
|
|
|
|
}
|
|
}
|
|
- call =
|
|
|
|
- [_wrappedChannel unmanagedCallWithPath:path completionQueue:queue callOptions:callOptions];
|
|
|
|
- if (call != NULL) {
|
|
|
|
- [_unmanagedCalls addObject:[NSValue valueWithPointer:call]];
|
|
|
|
|
|
+ _lastTimedDestroy = nil;
|
|
|
|
+
|
|
|
|
+ grpc_call *unmanagedCall = [_wrappedChannel unmanagedCallWithPath:path
|
|
|
|
+ completionQueue:[GRPCCompletionQueue completionQueue]
|
|
|
|
+ callOptions:callOptions];
|
|
|
|
+ if (unmanagedCall == NULL) {
|
|
|
|
+ NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object");
|
|
|
|
+ return nil;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self];
|
|
|
|
+ if (call == nil) {
|
|
|
|
+ NSAssert(call != nil, @"Unable to create GRPCWrappedCall object");
|
|
|
|
+ return nil;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ [_wrappedCalls addObject:call];
|
|
}
|
|
}
|
|
return call;
|
|
return call;
|
|
}
|
|
}
|
|
|
|
|
|
-- (void)destroyUnmanagedCall:(grpc_call *)unmanagedCall {
|
|
|
|
- if (unmanagedCall == NULL) {
|
|
|
|
|
|
+- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall {
|
|
|
|
+ NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty.");
|
|
|
|
+ if (wrappedCall == nil) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
-
|
|
|
|
- grpc_call_unref(unmanagedCall);
|
|
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
- NSValue *removedCall = [NSValue valueWithPointer:unmanagedCall];
|
|
|
|
- [_unmanagedCalls removeObject:removedCall];
|
|
|
|
- if ([_unmanagedCalls count] == 0) {
|
|
|
|
- _wrappedChannel = nil;
|
|
|
|
- GRPCChannelPool *strongPool = _channelPool;
|
|
|
|
- [strongPool unrefChannelWithConfiguration:_channelConfiguration];
|
|
|
|
|
|
+ if ([_wrappedCalls objectEnumerator].allObjects.count == 0) {
|
|
|
|
+ NSDate *now = [NSDate date];
|
|
|
|
+ _lastTimedDestroy = now;
|
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC),
|
|
|
|
+ _timerQueue, ^{
|
|
|
|
+ @synchronized(self) {
|
|
|
|
+ if (self->_lastTimedDestroy == now) {
|
|
|
|
+ self->_wrappedChannel = nil;
|
|
|
|
+ self->_lastTimedDestroy = nil;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- (void)disconnect {
|
|
- (void)disconnect {
|
|
|
|
+ NSHashTable<GRPCWrappedCall *> *copiedWrappedCalls = nil;
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
if (_wrappedChannel != nil) {
|
|
if (_wrappedChannel != nil) {
|
|
_wrappedChannel = nil;
|
|
_wrappedChannel = nil;
|
|
- [_unmanagedCalls removeAllObjects];
|
|
|
|
- GRPCChannelPool *strongPool = _channelPool;
|
|
|
|
- [strongPool unrefChannelWithConfiguration:_channelConfiguration];
|
|
|
|
|
|
+ copiedWrappedCalls = [_wrappedCalls copy];
|
|
|
|
+ [_wrappedCalls removeAllObjects];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ NSEnumerator *enumerator = [copiedWrappedCalls objectEnumerator];
|
|
|
|
+ GRPCWrappedCall *wrappedCall;
|
|
|
|
+ while ((wrappedCall = [enumerator nextObject])) {
|
|
|
|
+ [wrappedCall channelDisconnected];
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
@end
|
|
@end
|
|
|
|
|
|
@implementation GRPCPooledChannel (Test)
|
|
@implementation GRPCPooledChannel (Test)
|
|
|
|
|
|
|
|
+- (nullable instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration
|
|
|
|
+ destroyDelay:(NSTimeInterval)destroyDelay {
|
|
|
|
+ NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty.");
|
|
|
|
+ if (channelConfiguration == nil) {
|
|
|
|
+ return nil;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((self = [super init])) {
|
|
|
|
+ _channelConfiguration = channelConfiguration;
|
|
|
|
+ _destroyDelay = destroyDelay;
|
|
|
|
+ _wrappedCalls = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:1];
|
|
|
|
+ _wrappedChannel = nil;
|
|
|
|
+ _lastTimedDestroy = nil;
|
|
|
|
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
|
|
|
+ if (@available(iOS 8.0, macOS 10.10, *)) {
|
|
|
|
+ _timerQueue = dispatch_queue_create(NULL,
|
|
|
|
+ dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
|
|
|
|
+ } else {
|
|
|
|
+#else
|
|
|
|
+ {
|
|
|
|
+#endif
|
|
|
|
+ _timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return self;
|
|
|
|
+}
|
|
|
|
+
|
|
- (GRPCChannel *)wrappedChannel {
|
|
- (GRPCChannel *)wrappedChannel {
|
|
GRPCChannel *channel = nil;
|
|
GRPCChannel *channel = nil;
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
@@ -147,65 +185,28 @@ static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
|
|
|
|
|
|
@end
|
|
@end
|
|
|
|
|
|
-/**
|
|
|
|
- * A convenience value type for cached channel.
|
|
|
|
- */
|
|
|
|
-@interface GRPCChannelRecord : NSObject
|
|
|
|
-
|
|
|
|
-/** Pointer to the raw channel. May be nil when the channel has been destroyed. */
|
|
|
|
-@property GRPCChannel *channel;
|
|
|
|
-
|
|
|
|
-/** Channel proxy corresponding to this channel configuration. */
|
|
|
|
-@property GRPCPooledChannel *pooledChannel;
|
|
|
|
-
|
|
|
|
-/** Last time when a timed destroy is initiated on the channel. */
|
|
|
|
-@property NSDate *timedDestroyDate;
|
|
|
|
-
|
|
|
|
-/** Reference count of the proxy to the channel. */
|
|
|
|
-@property NSUInteger refCount;
|
|
|
|
-
|
|
|
|
-@end
|
|
|
|
-
|
|
|
|
-@implementation GRPCChannelRecord
|
|
|
|
-
|
|
|
|
-@end
|
|
|
|
-
|
|
|
|
@interface GRPCChannelPool ()
|
|
@interface GRPCChannelPool ()
|
|
|
|
|
|
-- (instancetype)initInstanceWithDestroyDelay:(NSTimeInterval)destroyDelay NS_DESIGNATED_INITIALIZER;
|
|
|
|
|
|
+- (instancetype)initInstance NS_DESIGNATED_INITIALIZER;
|
|
|
|
|
|
@end
|
|
@end
|
|
|
|
|
|
@implementation GRPCChannelPool {
|
|
@implementation GRPCChannelPool {
|
|
- NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannelRecord *> *_channelPool;
|
|
|
|
- dispatch_queue_t _dispatchQueue;
|
|
|
|
- NSTimeInterval _destroyDelay;
|
|
|
|
|
|
+ NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool;
|
|
}
|
|
}
|
|
|
|
|
|
+ (instancetype)sharedInstance {
|
|
+ (instancetype)sharedInstance {
|
|
dispatch_once(&gInitChannelPool, ^{
|
|
dispatch_once(&gInitChannelPool, ^{
|
|
gChannelPool =
|
|
gChannelPool =
|
|
- [[GRPCChannelPool alloc] initInstanceWithDestroyDelay:kDefaultChannelDestroyDelay];
|
|
|
|
|
|
+ [[GRPCChannelPool alloc] initInstance];
|
|
NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool.");
|
|
NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool.");
|
|
});
|
|
});
|
|
return gChannelPool;
|
|
return gChannelPool;
|
|
}
|
|
}
|
|
|
|
|
|
-- (instancetype)initInstanceWithDestroyDelay:(NSTimeInterval)destroyDelay {
|
|
|
|
|
|
+- (instancetype)initInstance {
|
|
if ((self = [super init])) {
|
|
if ((self = [super init])) {
|
|
_channelPool = [NSMutableDictionary dictionary];
|
|
_channelPool = [NSMutableDictionary dictionary];
|
|
-#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
|
|
|
- if (@available(iOS 8.0, macOS 10.10, *)) {
|
|
|
|
- _dispatchQueue = dispatch_queue_create(
|
|
|
|
- NULL,
|
|
|
|
- dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
|
|
|
|
- } else {
|
|
|
|
-#else
|
|
|
|
- {
|
|
|
|
-#endif
|
|
|
|
- _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
|
|
|
- }
|
|
|
|
- _destroyDelay = destroyDelay;
|
|
|
|
|
|
|
|
// Connectivity monitor is not required for CFStream
|
|
// Connectivity monitor is not required for CFStream
|
|
char *enableCFStream = getenv(kCFStreamVarName);
|
|
char *enableCFStream = getenv(kCFStreamVarName);
|
|
@@ -231,86 +232,23 @@ static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
|
|
GRPCChannelConfiguration *configuration =
|
|
GRPCChannelConfiguration *configuration =
|
|
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
|
|
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
- GRPCChannelRecord *record = _channelPool[configuration];
|
|
|
|
- if (record == nil) {
|
|
|
|
- record = [[GRPCChannelRecord alloc] init];
|
|
|
|
- record.pooledChannel =
|
|
|
|
- [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration channelPool:self];
|
|
|
|
- _channelPool[configuration] = record;
|
|
|
|
- pooledChannel = record.pooledChannel;
|
|
|
|
- } else {
|
|
|
|
- pooledChannel = record.pooledChannel;
|
|
|
|
|
|
+ pooledChannel = _channelPool[configuration];
|
|
|
|
+ if (pooledChannel == nil) {
|
|
|
|
+ pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration];
|
|
|
|
+ _channelPool[configuration] = pooledChannel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return pooledChannel;
|
|
return pooledChannel;
|
|
}
|
|
}
|
|
|
|
|
|
-- (GRPCChannel *)refChannelWithConfiguration:(GRPCChannelConfiguration *)configuration {
|
|
|
|
- GRPCChannel *ret = nil;
|
|
|
|
- @synchronized(self) {
|
|
|
|
- NSAssert(configuration != nil, @"configuration cannot be empty.");
|
|
|
|
- if (configuration == nil) {
|
|
|
|
- return nil;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- GRPCChannelRecord *record = _channelPool[configuration];
|
|
|
|
- NSAssert(record != nil, @"No record corresponding to a proxy.");
|
|
|
|
- if (record == nil) {
|
|
|
|
- return nil;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- record.refCount++;
|
|
|
|
- record.timedDestroyDate = nil;
|
|
|
|
- if (record.channel == nil) {
|
|
|
|
- // Channel is already destroyed;
|
|
|
|
- record.channel = [[GRPCChannel alloc] initWithChannelConfiguration:configuration];
|
|
|
|
- }
|
|
|
|
- ret = record.channel;
|
|
|
|
- }
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-- (void)unrefChannelWithConfiguration:(GRPCChannelConfiguration *)configuration {
|
|
|
|
- @synchronized(self) {
|
|
|
|
- GRPCChannelRecord *record = _channelPool[configuration];
|
|
|
|
- NSAssert(record != nil, @"No record corresponding to a proxy.");
|
|
|
|
- if (record == nil) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- NSAssert(record.refCount > 0, @"Inconsistent channel refcount.");
|
|
|
|
- if (record.refCount > 0) {
|
|
|
|
- record.refCount--;
|
|
|
|
- if (record.refCount == 0) {
|
|
|
|
- NSDate *now = [NSDate date];
|
|
|
|
- record.timedDestroyDate = now;
|
|
|
|
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_destroyDelay * NSEC_PER_SEC)),
|
|
|
|
- _dispatchQueue, ^{
|
|
|
|
- @synchronized(self) {
|
|
|
|
- if (now == record.timedDestroyDate) {
|
|
|
|
- // Destroy the raw channel and reset related records.
|
|
|
|
- record.timedDestroyDate = nil;
|
|
|
|
- record.channel = nil;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
- (void)disconnectAllChannels {
|
|
- (void)disconnectAllChannels {
|
|
- NSMutableSet<GRPCPooledChannel *> *proxySet = [NSMutableSet set];
|
|
|
|
|
|
+ NSDictionary *copiedPooledChannels;
|
|
@synchronized(self) {
|
|
@synchronized(self) {
|
|
- [_channelPool
|
|
|
|
- enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key,
|
|
|
|
- GRPCChannelRecord *_Nonnull obj, BOOL *_Nonnull stop) {
|
|
|
|
- obj.channel = nil;
|
|
|
|
- obj.timedDestroyDate = nil;
|
|
|
|
- [proxySet addObject:obj.pooledChannel];
|
|
|
|
- }];
|
|
|
|
|
|
+ copiedPooledChannels = [NSDictionary dictionaryWithDictionary:_channelPool];
|
|
}
|
|
}
|
|
- // Disconnect proxies
|
|
|
|
- [proxySet enumerateObjectsUsingBlock:^(GRPCPooledChannel *_Nonnull obj, BOOL *_Nonnull stop) {
|
|
|
|
|
|
+
|
|
|
|
+ // Disconnect pooled channels.
|
|
|
|
+ [copiedPooledChannels enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
|
[obj disconnect];
|
|
[obj disconnect];
|
|
}];
|
|
}];
|
|
}
|
|
}
|
|
@@ -323,8 +261,8 @@ static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
|
|
|
|
|
|
@implementation GRPCChannelPool (Test)
|
|
@implementation GRPCChannelPool (Test)
|
|
|
|
|
|
-- (instancetype)initTestPoolWithDestroyDelay:(NSTimeInterval)destroyDelay {
|
|
|
|
- return [self initInstanceWithDestroyDelay:destroyDelay];
|
|
|
|
|
|
+- (instancetype)initTestPool {
|
|
|
|
+ return [self initInstance];
|
|
}
|
|
}
|
|
|
|
|
|
@end
|
|
@end
|