• 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
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