| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 | #region Copyright notice and license// 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.#endregionusing System;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;using Grpc.Core.Internal;using Grpc.Core.Logging;using Grpc.Core.Utils;namespace Grpc.Core{    /// <summary>    /// Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers.    /// More client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking    /// a remote call so in general you should reuse a single channel for as many calls as possible.    /// </summary>    public class Channel    {        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();        readonly object myLock = new object();        readonly AtomicCounter activeCallCounter = new AtomicCounter();        readonly CancellationTokenSource shutdownTokenSource = new CancellationTokenSource();        readonly string target;        readonly GrpcEnvironment environment;        readonly CompletionQueueSafeHandle completionQueue;        readonly ChannelSafeHandle handle;        readonly Dictionary<string, ChannelOption> options;        readonly Task connectivityWatcherTask;        bool shutdownRequested;        /// <summary>        /// Creates a channel that connects to a specific host.        /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.        /// </summary>        /// <param name="target">Target of the channel.</param>        /// <param name="credentials">Credentials to secure the channel.</param>        public Channel(string target, ChannelCredentials credentials) :            this(target, credentials, null)        {        }        /// <summary>        /// Creates a channel that connects to a specific host.        /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.        /// </summary>        /// <param name="target">Target of the channel.</param>        /// <param name="credentials">Credentials to secure the channel.</param>        /// <param name="options">Channel options.</param>        public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options)        {            this.target = GrpcPreconditions.CheckNotNull(target, "target");            this.options = CreateOptionsDictionary(options);            EnsureUserAgentChannelOption(this.options);            this.environment = GrpcEnvironment.AddRef();            this.completionQueue = this.environment.PickCompletionQueue();            using (var nativeCredentials = credentials.ToNativeCredentials())            using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))            {                if (nativeCredentials != null)                {                    this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);                }                else                {                    this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);                }            }            // TODO(jtattermusch): Workaround for https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822.            // Remove once retries are supported in C core            this.connectivityWatcherTask = RunConnectivityWatcherAsync();            GrpcEnvironment.RegisterChannel(this);        }        /// <summary>        /// Creates a channel that connects to a specific host and port.        /// </summary>        /// <param name="host">The name or IP address of the host.</param>        /// <param name="port">The port.</param>        /// <param name="credentials">Credentials to secure the channel.</param>        public Channel(string host, int port, ChannelCredentials credentials) :            this(host, port, credentials, null)        {        }        /// <summary>        /// Creates a channel that connects to a specific host and port.        /// </summary>        /// <param name="host">The name or IP address of the host.</param>        /// <param name="port">The port.</param>        /// <param name="credentials">Credentials to secure the channel.</param>        /// <param name="options">Channel options.</param>        public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options) :            this(string.Format("{0}:{1}", host, port), credentials, options)        {        }        /// <summary>        /// Gets current connectivity state of this channel.        /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned.        /// </summary>        public ChannelState State        {            get            {                return GetConnectivityState(false);            }        }        // cached handler for watch connectivity state        static readonly BatchCompletionDelegate WatchConnectivityStateHandler = (success, ctx, state) =>        {            var tcs = (TaskCompletionSource<bool>) state;            tcs.SetResult(success);        };        /// <summary>        /// Returned tasks completes once channel state has become different from         /// given lastObservedState.         /// If deadline is reached or and error occurs, returned task is cancelled.        /// </summary>        public async Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null)        {            var result = await WaitForStateChangedInternalAsync(lastObservedState, deadline).ConfigureAwait(false);            if (!result)            {                throw new TaskCanceledException("Reached deadline.");            }        }        /// <summary>        /// Returned tasks completes once channel state has become different from        /// given lastObservedState (<c>true</c> is returned) or if the wait has timed out (<c>false</c> is returned).        /// </summary>        internal Task<bool> WaitForStateChangedInternalAsync(ChannelState lastObservedState, DateTime? deadline = null)        {            GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown,                "Shutdown is a terminal state. No further state changes can occur.");            var tcs = new TaskCompletionSource<bool>();            var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;            lock (myLock)            {                if (handle.IsClosed)                {                    // If channel has been already shutdown and handle was disposed, we would end up with                    // an abandoned completion added to the completion registry. Instead, we make sure we fail early.                    throw new ObjectDisposedException(nameof(handle), "Channel handle has already been disposed.");                }                else                {                    // pass "tcs" as "state" for WatchConnectivityStateHandler.                    handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, WatchConnectivityStateHandler, tcs);                }            }            return tcs.Task;        }        /// <summary>Resolved address of the remote endpoint in URI format.</summary>        public string ResolvedTarget        {            get            {                return handle.GetTarget();            }        }        /// <summary>The original target used to create the channel.</summary>        public string Target        {            get            {                return this.target;            }        }        /// <summary>        /// Returns a token that gets cancelled once <c>ShutdownAsync</c> is invoked.        /// </summary>        public CancellationToken ShutdownToken        {            get            {                return this.shutdownTokenSource.Token;            }        }        /// <summary>        /// Allows explicitly requesting channel to connect without starting an RPC.        /// Returned task completes once state Ready was seen. If the deadline is reached,        /// or channel enters the Shutdown state, the task is cancelled.        /// There is no need to call this explicitly unless your use case requires that.        /// Starting an RPC on a new channel will request connection implicitly.        /// </summary>        /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param>        public async Task ConnectAsync(DateTime? deadline = null)        {            var currentState = GetConnectivityState(true);            while (currentState != ChannelState.Ready)            {                if (currentState == ChannelState.Shutdown)                {                    throw new OperationCanceledException("Channel has reached Shutdown state.");                }                await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);                currentState = GetConnectivityState(false);            }        }        /// <summary>        /// Shuts down the channel cleanly. It is strongly recommended to shutdown        /// all previously created channels before exiting from the process.        /// </summary>        /// <remarks>        /// This method doesn't wait for all calls on this channel to finish (nor does        /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure        /// all the calls on this channel have finished (successfully or with an error)        /// before shutting down the channel to ensure channel shutdown won't impact        /// the outcome of those remote calls.        /// </remarks>        public async Task ShutdownAsync()        {            lock (myLock)            {                GrpcPreconditions.CheckState(!shutdownRequested);                shutdownRequested = true;            }            GrpcEnvironment.UnregisterChannel(this);            shutdownTokenSource.Cancel();            var activeCallCount = activeCallCounter.Count;            if (activeCallCount > 0)            {                Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);            }            lock (myLock)            {                handle.Dispose();            }            await Task.WhenAll(GrpcEnvironment.ReleaseAsync(), connectivityWatcherTask).ConfigureAwait(false);        }        internal ChannelSafeHandle Handle        {            get            {                return this.handle;            }        }        internal GrpcEnvironment Environment        {            get            {                return this.environment;            }        }        internal CompletionQueueSafeHandle CompletionQueue        {            get            {                return this.completionQueue;            }        }        internal void AddCallReference(object call)        {            activeCallCounter.Increment();            bool success = false;            handle.DangerousAddRef(ref success);            GrpcPreconditions.CheckState(success);        }        internal void RemoveCallReference(object call)        {            handle.DangerousRelease();            activeCallCounter.Decrement();        }        private ChannelState GetConnectivityState(bool tryToConnect)        {            try            {                lock (myLock)                {                    return handle.CheckConnectivityState(tryToConnect);                }            }            catch (ObjectDisposedException)            {                return ChannelState.Shutdown;            }        }        /// <summary>        /// Constantly Watches channel connectivity status to work around https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822        /// </summary>        private async Task RunConnectivityWatcherAsync()        {            try            {                var lastState = State;                while (lastState != ChannelState.Shutdown)                {                    lock (myLock)                    {                        if (shutdownRequested)                        {                            break;                        }                    }                    // ignore the result                    await WaitForStateChangedInternalAsync(lastState, DateTime.UtcNow.AddSeconds(1)).ConfigureAwait(false);                    lastState = State;                }            }            catch (ObjectDisposedException) {                // during shutdown, channel is going to be disposed.            }        }        private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)        {            var key = ChannelOptions.PrimaryUserAgentString;            var userAgentString = "";            ChannelOption option;            if (options.TryGetValue(key, out option))            {                // user-provided userAgentString needs to be at the beginning                userAgentString = option.StringValue + " ";            };            // TODO(jtattermusch): it would be useful to also provide .NET/mono version.            userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);            options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);        }        private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)        {            var dict = new Dictionary<string, ChannelOption>();            if (options == null)            {                return dict;            }            foreach (var option in options)            {                dict.Add(option.Name, option);            }            return dict;        }    }}
 |