| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 | /* * * Copyright 2015 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */#import "GRPCChannel.h"#include <grpc/support/log.h>#import "ChannelArgsUtil.h"#import "GRPCChannelFactory.h"#import "GRPCChannelPool.h"#import "GRPCCompletionQueue.h"#import "GRPCCronetChannelFactory.h"#import "GRPCInsecureChannelFactory.h"#import "GRPCSecureChannelFactory.h"#import "version.h"#import <GRPCClient/GRPCCall+Cronet.h>#import <GRPCClient/GRPCCallOptions.h>/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */NSTimeInterval kChannelDestroyDelay = 30;/** Global instance of channel pool. */static GRPCChannelPool *gChannelPool;/** * Time the channel destroy when the channel's calls are unreffed. If there's new call, reset the * timer. */@interface GRPCChannelRef : NSObject- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay              destroyChannelCallback:(void (^)())destroyChannelCallback;/** Add call ref count to the channel and maybe reset the timer. */- (void)refChannel;/** Reduce call ref count to the channel and maybe set the timer. */- (void)unrefChannel;/** Disconnect the channel immediately. */- (void)disconnect;@end@implementation GRPCChannelRef {  NSTimeInterval _destroyDelay;  void (^_destroyChannelCallback)();  NSUInteger _refCount;  BOOL _disconnected;  dispatch_queue_t _dispatchQueue;  /**   * Date and time when last timer is scheduled. When a timer is fired, if   * _lastDispatch + _destroyDelay < now, it can be determined that another timer is scheduled after   * schedule of the current timer, hence the current one should be discarded.   */  NSDate *_lastDispatch;}- (instancetype)initWithDestroyDelay:(NSTimeInterval)destroyDelay              destroyChannelCallback:(void (^)())destroyChannelCallback {  if ((self = [super init])) {    _destroyDelay = destroyDelay;    _destroyChannelCallback = destroyChannelCallback;    _refCount = 1;    _disconnected = NO;    if (@available(iOS 8.0, *)) {      _dispatchQueue = dispatch_queue_create(          NULL,          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1));    } else {      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);    }    _lastDispatch = nil;  }  return self;}- (void)refChannel {  dispatch_async(_dispatchQueue, ^{    if (!self->_disconnected) {      self->_refCount++;      self->_lastDispatch = nil;    }  });}- (void)unrefChannel {  dispatch_async(_dispatchQueue, ^{    if (!self->_disconnected) {      self->_refCount--;      if (self->_refCount == 0) {        NSDate *now = [NSDate date];        self->_lastDispatch = now;        dispatch_time_t delay =            dispatch_time(DISPATCH_TIME_NOW, (int64_t)self->_destroyDelay * NSEC_PER_SEC);        dispatch_after(delay, self->_dispatchQueue, ^{          [self timerFireWithScheduleDate:now];        });      }    }  });}- (void)disconnect {  dispatch_async(_dispatchQueue, ^{    if (!self->_disconnected) {      self->_lastDispatch = nil;      self->_disconnected = YES;      // Break retain loop      self->_destroyChannelCallback = nil;    }  });}- (void)timerFireWithScheduleDate:(NSDate *)scheduleDate {  dispatch_async(_dispatchQueue, ^{    if (self->_disconnected || self->_lastDispatch != scheduleDate) {      return;    }    self->_lastDispatch = nil;    self->_disconnected = YES;    self->_destroyChannelCallback();    // Break retain loop    self->_destroyChannelCallback = nil;  });}@end@implementation GRPCChannel {  GRPCChannelConfiguration *_configuration;  grpc_channel *_unmanagedChannel;  GRPCChannelRef *_channelRef;  dispatch_queue_t _dispatchQueue;}- (grpc_call *)unmanagedCallWithPath:(NSString *)path                     completionQueue:(GRPCCompletionQueue *)queue                         callOptions:(GRPCCallOptions *)callOptions {  NSAssert(path.length, @"path must not be empty.");  NSAssert(queue, @"completionQueue must not be empty.");  NSAssert(callOptions, @"callOptions must not be empty.");  __block grpc_call *call = nil;  dispatch_sync(_dispatchQueue, ^{    if (self->_unmanagedChannel) {      NSString *serverAuthority =          callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority;      NSTimeInterval timeout = callOptions.timeout;      NSAssert(timeout >= 0, @"Invalid timeout");      grpc_slice host_slice = grpc_empty_slice();      if (serverAuthority) {        host_slice = grpc_slice_from_copied_string(serverAuthority.UTF8String);      }      grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);      gpr_timespec deadline_ms =          timeout == 0              ? gpr_inf_future(GPR_CLOCK_REALTIME)              : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),                             gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));      call = grpc_channel_create_call(self->_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,                                      queue.unmanagedQueue, path_slice,                                      serverAuthority ? &host_slice : NULL, deadline_ms, NULL);      if (serverAuthority) {        grpc_slice_unref(host_slice);      }      grpc_slice_unref(path_slice);    }  });  return call;}- (void)ref {  dispatch_async(_dispatchQueue, ^{    if (self->_unmanagedChannel) {      [self->_channelRef refChannel];    }  });}- (void)unref {  dispatch_async(_dispatchQueue, ^{    if (self->_unmanagedChannel) {      [self->_channelRef unrefChannel];    }  });}- (void)disconnect {  dispatch_async(_dispatchQueue, ^{    if (self->_unmanagedChannel) {      grpc_channel_destroy(self->_unmanagedChannel);      self->_unmanagedChannel = nil;      [self->_channelRef disconnect];    }  });}- (void)destroyChannel {  dispatch_async(_dispatchQueue, ^{    if (self->_unmanagedChannel) {      grpc_channel_destroy(self->_unmanagedChannel);      self->_unmanagedChannel = nil;      [gChannelPool removeChannel:self];    }  });}- (nullable instancetype)initWithUnmanagedChannel:(grpc_channel *_Nullable)unmanagedChannel                                    configuration:(GRPCChannelConfiguration *)configuration {  NSAssert(configuration, @"Configuration must not be empty.");  if (!unmanagedChannel) {    return nil;  }  if ((self = [super init])) {    _unmanagedChannel = unmanagedChannel;    _configuration = [configuration copy];    _channelRef = [[GRPCChannelRef alloc] initWithDestroyDelay:kChannelDestroyDelay                                        destroyChannelCallback:^{                                          [self destroyChannel];                                        }];    if (@available(iOS 8.0, *)) {      _dispatchQueue = dispatch_queue_create(          NULL,          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1));    } else {      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);    }  }  return self;}- (void)dealloc {  if (_unmanagedChannel) {    grpc_channel_destroy(_unmanagedChannel);  }}+ (nullable instancetype)createChannelWithConfiguration:(GRPCChannelConfiguration *)config {  NSString *host = config.host;  if (host.length == 0) {    return nil;  }  NSDictionary *channelArgs;  if (config.callOptions.additionalChannelArgs.count != 0) {    NSMutableDictionary *args = [config.channelArgs mutableCopy];    [args addEntriesFromDictionary:config.callOptions.additionalChannelArgs];    channelArgs = args;  } else {    channelArgs = config.channelArgs;  }  id<GRPCChannelFactory> factory = config.channelFactory;  grpc_channel *unmanaged_channel = [factory createChannelWithHost:host channelArgs:channelArgs];  return [[GRPCChannel alloc] initWithUnmanagedChannel:unmanaged_channel configuration:config];}+ (nullable instancetype)channelWithHost:(NSString *)host                             callOptions:(GRPCCallOptions *)callOptions {  static dispatch_once_t initChannelPool;  dispatch_once(&initChannelPool, ^{    gChannelPool = [[GRPCChannelPool alloc] init];  });  NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];  if (hostURL.host && !hostURL.port) {    host = [hostURL.host stringByAppendingString:@":443"];  }  GRPCChannelConfiguration *channelConfig =      [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];  return [gChannelPool channelWithConfiguration:channelConfig];}+ (void)closeOpenConnections {  [gChannelPool removeAndCloseAllChannels];}@end
 |