• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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