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