Channel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #region Copyright notice and license
  2. // Copyright 2015, Google Inc.
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are
  7. // met:
  8. //
  9. // * Redistributions of source code must retain the above copyright
  10. // notice, this list of conditions and the following disclaimer.
  11. // * Redistributions in binary form must reproduce the above
  12. // copyright notice, this list of conditions and the following disclaimer
  13. // in the documentation and/or other materials provided with the
  14. // distribution.
  15. // * Neither the name of Google Inc. nor the names of its
  16. // contributors may be used to endorse or promote products derived from
  17. // this software without specific prior written permission.
  18. //
  19. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. #endregion
  31. using System;
  32. using System.Collections.Generic;
  33. using System.Threading;
  34. using System.Threading.Tasks;
  35. using Grpc.Core.Internal;
  36. using Grpc.Core.Logging;
  37. using Grpc.Core.Utils;
  38. namespace Grpc.Core
  39. {
  40. /// <summary>
  41. /// Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers.
  42. /// More client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking
  43. /// a remote call so in general you should reuse a single channel for as many calls as possible.
  44. /// </summary>
  45. public class Channel
  46. {
  47. static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
  48. readonly object myLock = new object();
  49. readonly AtomicCounter activeCallCounter = new AtomicCounter();
  50. readonly CancellationTokenSource shutdownTokenSource = new CancellationTokenSource();
  51. readonly string target;
  52. readonly GrpcEnvironment environment;
  53. readonly CompletionQueueSafeHandle completionQueue;
  54. readonly ChannelSafeHandle handle;
  55. readonly Dictionary<string, ChannelOption> options;
  56. bool shutdownRequested;
  57. /// <summary>
  58. /// Creates a channel that connects to a specific host.
  59. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  60. /// </summary>
  61. /// <param name="target">Target of the channel.</param>
  62. /// <param name="credentials">Credentials to secure the channel.</param>
  63. public Channel(string target, ChannelCredentials credentials) :
  64. this(target, credentials, null)
  65. {
  66. }
  67. /// <summary>
  68. /// Creates a channel that connects to a specific host.
  69. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  70. /// </summary>
  71. /// <param name="target">Target of the channel.</param>
  72. /// <param name="credentials">Credentials to secure the channel.</param>
  73. /// <param name="options">Channel options.</param>
  74. public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options)
  75. {
  76. this.target = GrpcPreconditions.CheckNotNull(target, "target");
  77. this.options = CreateOptionsDictionary(options);
  78. EnsureUserAgentChannelOption(this.options);
  79. this.environment = GrpcEnvironment.AddRef();
  80. this.completionQueue = this.environment.PickCompletionQueue();
  81. using (var nativeCredentials = credentials.ToNativeCredentials())
  82. using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
  83. {
  84. if (nativeCredentials != null)
  85. {
  86. this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
  87. }
  88. else
  89. {
  90. this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
  91. }
  92. }
  93. GrpcEnvironment.RegisterChannel(this);
  94. }
  95. /// <summary>
  96. /// Creates a channel that connects to a specific host and port.
  97. /// </summary>
  98. /// <param name="host">The name or IP address of the host.</param>
  99. /// <param name="port">The port.</param>
  100. /// <param name="credentials">Credentials to secure the channel.</param>
  101. public Channel(string host, int port, ChannelCredentials credentials) :
  102. this(host, port, credentials, null)
  103. {
  104. }
  105. /// <summary>
  106. /// Creates a channel that connects to a specific host and port.
  107. /// </summary>
  108. /// <param name="host">The name or IP address of the host.</param>
  109. /// <param name="port">The port.</param>
  110. /// <param name="credentials">Credentials to secure the channel.</param>
  111. /// <param name="options">Channel options.</param>
  112. public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options) :
  113. this(string.Format("{0}:{1}", host, port), credentials, options)
  114. {
  115. }
  116. /// <summary>
  117. /// Gets current connectivity state of this channel.
  118. /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned.
  119. /// </summary>
  120. public ChannelState State
  121. {
  122. get
  123. {
  124. return GetConnectivityState(false);
  125. }
  126. }
  127. /// <summary>
  128. /// Returned tasks completes once channel state has become different from
  129. /// given lastObservedState.
  130. /// If deadline is reached or and error occurs, returned task is cancelled.
  131. /// </summary>
  132. public Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null)
  133. {
  134. GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown,
  135. "Shutdown is a terminal state. No further state changes can occur.");
  136. var tcs = new TaskCompletionSource<object>();
  137. var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;
  138. var handler = new BatchCompletionDelegate((success, ctx) =>
  139. {
  140. if (success)
  141. {
  142. tcs.SetResult(null);
  143. }
  144. else
  145. {
  146. tcs.SetCanceled();
  147. }
  148. });
  149. handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, handler);
  150. return tcs.Task;
  151. }
  152. /// <summary>Resolved address of the remote endpoint in URI format.</summary>
  153. public string ResolvedTarget
  154. {
  155. get
  156. {
  157. return handle.GetTarget();
  158. }
  159. }
  160. /// <summary>The original target used to create the channel.</summary>
  161. public string Target
  162. {
  163. get
  164. {
  165. return this.target;
  166. }
  167. }
  168. /// <summary>
  169. /// Returns a token that gets cancelled once <c>ShutdownAsync</c> is invoked.
  170. /// </summary>
  171. public CancellationToken ShutdownToken
  172. {
  173. get
  174. {
  175. return this.shutdownTokenSource.Token;
  176. }
  177. }
  178. /// <summary>
  179. /// Allows explicitly requesting channel to connect without starting an RPC.
  180. /// Returned task completes once state Ready was seen. If the deadline is reached,
  181. /// or channel enters the Shutdown state, the task is cancelled.
  182. /// There is no need to call this explicitly unless your use case requires that.
  183. /// Starting an RPC on a new channel will request connection implicitly.
  184. /// </summary>
  185. /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param>
  186. public async Task ConnectAsync(DateTime? deadline = null)
  187. {
  188. var currentState = GetConnectivityState(true);
  189. while (currentState != ChannelState.Ready)
  190. {
  191. if (currentState == ChannelState.Shutdown)
  192. {
  193. throw new OperationCanceledException("Channel has reached Shutdown state.");
  194. }
  195. await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);
  196. currentState = GetConnectivityState(false);
  197. }
  198. }
  199. /// <summary>
  200. /// Shuts down the channel cleanly. It is strongly recommended to shutdown
  201. /// all previously created channels before exiting from the process.
  202. /// </summary>
  203. /// <remarks>
  204. /// This method doesn't wait for all calls on this channel to finish (nor does
  205. /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure
  206. /// all the calls on this channel have finished (successfully or with an error)
  207. /// before shutting down the channel to ensure channel shutdown won't impact
  208. /// the outcome of those remote calls.
  209. /// </remarks>
  210. public async Task ShutdownAsync()
  211. {
  212. lock (myLock)
  213. {
  214. GrpcPreconditions.CheckState(!shutdownRequested);
  215. shutdownRequested = true;
  216. }
  217. GrpcEnvironment.UnregisterChannel(this);
  218. shutdownTokenSource.Cancel();
  219. var activeCallCount = activeCallCounter.Count;
  220. if (activeCallCount > 0)
  221. {
  222. Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
  223. }
  224. handle.Dispose();
  225. await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false);
  226. }
  227. internal ChannelSafeHandle Handle
  228. {
  229. get
  230. {
  231. return this.handle;
  232. }
  233. }
  234. internal GrpcEnvironment Environment
  235. {
  236. get
  237. {
  238. return this.environment;
  239. }
  240. }
  241. internal CompletionQueueSafeHandle CompletionQueue
  242. {
  243. get
  244. {
  245. return this.completionQueue;
  246. }
  247. }
  248. internal void AddCallReference(object call)
  249. {
  250. activeCallCounter.Increment();
  251. bool success = false;
  252. handle.DangerousAddRef(ref success);
  253. GrpcPreconditions.CheckState(success);
  254. }
  255. internal void RemoveCallReference(object call)
  256. {
  257. handle.DangerousRelease();
  258. activeCallCounter.Decrement();
  259. }
  260. private ChannelState GetConnectivityState(bool tryToConnect)
  261. {
  262. try
  263. {
  264. return handle.CheckConnectivityState(tryToConnect);
  265. }
  266. catch (ObjectDisposedException)
  267. {
  268. return ChannelState.Shutdown;
  269. }
  270. }
  271. private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
  272. {
  273. var key = ChannelOptions.PrimaryUserAgentString;
  274. var userAgentString = "";
  275. ChannelOption option;
  276. if (options.TryGetValue(key, out option))
  277. {
  278. // user-provided userAgentString needs to be at the beginning
  279. userAgentString = option.StringValue + " ";
  280. };
  281. // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
  282. userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
  283. options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);
  284. }
  285. private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)
  286. {
  287. var dict = new Dictionary<string, ChannelOption>();
  288. if (options == null)
  289. {
  290. return dict;
  291. }
  292. foreach (var option in options)
  293. {
  294. dict.Add(option.Name, option);
  295. }
  296. return dict;
  297. }
  298. }
  299. }