1/* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19#import <Foundation/Foundation.h> 20 21#import "../../internal/GRPCCallOptions+Internal.h" 22#import "GRPCChannel.h" 23#import "GRPCChannelFactory.h" 24#import "GRPCChannelPool+Test.h" 25#import "GRPCChannelPool.h" 26#import "GRPCCompletionQueue.h" 27#import "GRPCInsecureChannelFactory.h" 28#import "GRPCSecureChannelFactory.h" 29#import "GRPCWrappedCall.h" 30 31extern const char *kCFStreamVarName; 32 33static GRPCChannelPool *gChannelPool; 34static dispatch_once_t gInitChannelPool; 35 36/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */ 37static const NSTimeInterval kDefaultChannelDestroyDelay = 30; 38 39@implementation GRPCPooledChannel { 40 GRPCChannelConfiguration *_channelConfiguration; 41 NSTimeInterval _destroyDelay; 42 43 NSHashTable<GRPCWrappedCall *> *_wrappedCalls; 44 GRPCChannel *_wrappedChannel; 45 NSDate *_lastTimedDestroy; 46 dispatch_queue_t _timerQueue; 47} 48 49- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration { 50 return [self initWithChannelConfiguration:channelConfiguration 51 destroyDelay:kDefaultChannelDestroyDelay]; 52} 53 54- (nullable instancetype)initWithChannelConfiguration: 55 (GRPCChannelConfiguration *)channelConfiguration 56 destroyDelay:(NSTimeInterval)destroyDelay { 57 NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty."); 58 if (channelConfiguration == nil) { 59 return nil; 60 } 61 62 if ((self = [super init])) { 63 _channelConfiguration = [channelConfiguration copy]; 64 _destroyDelay = destroyDelay; 65 _wrappedCalls = [NSHashTable weakObjectsHashTable]; 66 _wrappedChannel = nil; 67 _lastTimedDestroy = nil; 68#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 69 if (@available(iOS 8.0, macOS 10.10, *)) { 70 _timerQueue = dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class( 71 DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)); 72 } else { 73#else 74 { 75#endif 76 _timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); 77 } 78 } 79 80 return self; 81} 82 83- (void)dealloc { 84 // Disconnect GRPCWrappedCall objects created but not yet removed 85 if (_wrappedCalls.allObjects.count != 0) { 86 for (GRPCWrappedCall *wrappedCall in _wrappedCalls.allObjects) { 87 [wrappedCall channelDisconnected]; 88 }; 89 } 90} 91 92- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path 93 completionQueue:(GRPCCompletionQueue *)queue 94 callOptions:(GRPCCallOptions *)callOptions { 95 NSAssert(path.length > 0, @"path must not be empty."); 96 NSAssert(queue != nil, @"completionQueue must not be empty."); 97 NSAssert(callOptions, @"callOptions must not be empty."); 98 if (path.length == 0 || queue == nil || callOptions == nil) { 99 return nil; 100 } 101 102 GRPCWrappedCall *call = nil; 103 104 @synchronized(self) { 105 if (_wrappedChannel == nil) { 106 _wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration]; 107 if (_wrappedChannel == nil) { 108 NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy."); 109 return nil; 110 } 111 } 112 _lastTimedDestroy = nil; 113 114 grpc_call *unmanagedCall = 115 [_wrappedChannel unmanagedCallWithPath:path 116 completionQueue:[GRPCCompletionQueue completionQueue] 117 callOptions:callOptions]; 118 if (unmanagedCall == NULL) { 119 NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object"); 120 return nil; 121 } 122 123 call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self]; 124 if (call == nil) { 125 NSAssert(call != nil, @"Unable to create GRPCWrappedCall object"); 126 grpc_call_unref(unmanagedCall); 127 return nil; 128 } 129 130 [_wrappedCalls addObject:call]; 131 } 132 return call; 133} 134 135- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall { 136 NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty."); 137 if (wrappedCall == nil) { 138 return; 139 } 140 @synchronized(self) { 141 // Detect if all objects weakly referenced in _wrappedCalls are (implicitly) removed. 142 // _wrappedCalls.count does not work here since the hash table may include deallocated weak 143 // references. _wrappedCalls.allObjects forces removal of those objects. 144 if (_wrappedCalls.allObjects.count == 0) { 145 // No more call has reference to this channel. We may start the timer for destroying the 146 // channel now. 147 NSDate *now = [NSDate date]; 148 NSAssert(now != nil, @"Unable to create NSDate object 'now'."); 149 _lastTimedDestroy = now; 150 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC), 151 _timerQueue, ^{ 152 @synchronized(self) { 153 // Check _lastTimedDestroy against now in case more calls are created (and 154 // maybe destroyed) after this dispatch_async. In that case the current 155 // dispatch_after block should be discarded; the channel should be 156 // destroyed in a later dispatch_after block. 157 if (now != nil && self->_lastTimedDestroy == now) { 158 self->_wrappedChannel = nil; 159 self->_lastTimedDestroy = nil; 160 } 161 } 162 }); 163 } 164 } 165} 166 167- (void)disconnect { 168 NSArray<GRPCWrappedCall *> *copiedWrappedCalls = nil; 169 @synchronized(self) { 170 if (_wrappedChannel != nil) { 171 _wrappedChannel = nil; 172 copiedWrappedCalls = _wrappedCalls.allObjects; 173 [_wrappedCalls removeAllObjects]; 174 } 175 } 176 for (GRPCWrappedCall *wrappedCall in copiedWrappedCalls) { 177 [wrappedCall channelDisconnected]; 178 } 179} 180 181- (GRPCChannel *)wrappedChannel { 182 GRPCChannel *channel = nil; 183 @synchronized(self) { 184 channel = _wrappedChannel; 185 } 186 return channel; 187} 188 189@end 190 191@interface GRPCChannelPool () 192 193- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER; 194 195@end 196 197@implementation GRPCChannelPool { 198 NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool; 199} 200 201+ (instancetype)sharedInstance { 202 dispatch_once(&gInitChannelPool, ^{ 203 gChannelPool = [[GRPCChannelPool alloc] initPrivate]; 204 NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool."); 205 }); 206 return gChannelPool; 207} 208 209- (instancetype)initPrivate { 210 if ((self = [super init])) { 211 _channelPool = [NSMutableDictionary dictionary]; 212 } 213 return self; 214} 215 216- (void)dealloc { 217} 218 219- (GRPCPooledChannel *)channelWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { 220 NSAssert(host.length > 0, @"Host must not be empty."); 221 NSAssert(callOptions != nil, @"callOptions must not be empty."); 222 if (host.length == 0 || callOptions == nil) { 223 return nil; 224 } 225 226 // remove trailing slash of hostname if a supported scheme is not provided 227 if (![host hasPrefix:@"dns:"] && ![host hasPrefix:@"unix:"] && ![host hasPrefix:@"ipv4:"] && 228 ![host hasPrefix:@"ipv6:"]) { 229 NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]]; 230 if (hostURL.host && hostURL.port == nil) { 231 host = [hostURL.host stringByAppendingString:@":443"]; 232 } 233 } 234 235 GRPCPooledChannel *pooledChannel = nil; 236 GRPCChannelConfiguration *configuration = 237 [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions]; 238 @synchronized(self) { 239 pooledChannel = _channelPool[configuration]; 240 if (pooledChannel == nil) { 241 pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration]; 242 _channelPool[configuration] = pooledChannel; 243 } 244 } 245 return pooledChannel; 246} 247 248- (void)disconnectAllChannels { 249 NSArray<GRPCPooledChannel *> *copiedPooledChannels; 250 @synchronized(self) { 251 copiedPooledChannels = _channelPool.allValues; 252 } 253 254 // Disconnect pooled channels. 255 for (GRPCPooledChannel *pooledChannel in copiedPooledChannels) { 256 [pooledChannel disconnect]; 257 } 258} 259 260@end 261 262@implementation GRPCChannelPool (Test) 263 264- (instancetype)initTestPool { 265 return [self initPrivate]; 266} 267 268@end 269