Channel.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. readonly Task connectivityWatcherTask;
  57. bool shutdownRequested;
  58. /// <summary>
  59. /// Creates a channel that connects to a specific host.
  60. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  61. /// </summary>
  62. /// <param name="target">Target of the channel.</param>
  63. /// <param name="credentials">Credentials to secure the channel.</param>
  64. public Channel(string target, ChannelCredentials credentials) :
  65. this(target, credentials, null)
  66. {
  67. }
  68. /// <summary>
  69. /// Creates a channel that connects to a specific host.
  70. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  71. /// </summary>
  72. /// <param name="target">Target of the channel.</param>
  73. /// <param name="credentials">Credentials to secure the channel.</param>
  74. /// <param name="options">Channel options.</param>
  75. public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options)
  76. {
  77. this.target = GrpcPreconditions.CheckNotNull(target, "target");
  78. this.options = CreateOptionsDictionary(options);
  79. EnsureUserAgentChannelOption(this.options);
  80. this.environment = GrpcEnvironment.AddRef();
  81. this.completionQueue = this.environment.PickCompletionQueue();
  82. using (var nativeCredentials = credentials.ToNativeCredentials())
  83. using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
  84. {
  85. if (nativeCredentials != null)
  86. {
  87. this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
  88. }
  89. else
  90. {
  91. this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
  92. }
  93. }
  94. // TODO(jtattermusch): Workaround for https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822.
  95. // Remove once retries are supported in C core
  96. this.connectivityWatcherTask = RunConnectivityWatcherAsync();
  97. GrpcEnvironment.RegisterChannel(this);
  98. }
  99. /// <summary>
  100. /// Creates a channel that connects to a specific host and port.
  101. /// </summary>
  102. /// <param name="host">The name or IP address of the host.</param>
  103. /// <param name="port">The port.</param>
  104. /// <param name="credentials">Credentials to secure the channel.</param>
  105. public Channel(string host, int port, ChannelCredentials credentials) :
  106. this(host, port, credentials, null)
  107. {
  108. }
  109. /// <summary>
  110. /// Creates a channel that connects to a specific host and port.
  111. /// </summary>
  112. /// <param name="host">The name or IP address of the host.</param>
  113. /// <param name="port">The port.</param>
  114. /// <param name="credentials">Credentials to secure the channel.</param>
  115. /// <param name="options">Channel options.</param>
  116. public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options) :
  117. this(string.Format("{0}:{1}", host, port), credentials, options)
  118. {
  119. }
  120. /// <summary>
  121. /// Gets current connectivity state of this channel.
  122. /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned.
  123. /// </summary>
  124. public ChannelState State
  125. {
  126. get
  127. {
  128. return GetConnectivityState(false);
  129. }
  130. }
  131. /// <summary>
  132. /// Returned tasks completes once channel state has become different from
  133. /// given lastObservedState.
  134. /// If deadline is reached or and error occurs, returned task is cancelled.
  135. /// </summary>
  136. public Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null)
  137. {
  138. GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown,
  139. "Shutdown is a terminal state. No further state changes can occur.");
  140. var tcs = new TaskCompletionSource<object>();
  141. var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;
  142. var handler = new BatchCompletionDelegate((success, ctx) =>
  143. {
  144. if (success)
  145. {
  146. tcs.SetResult(null);
  147. }
  148. else
  149. {
  150. tcs.SetCanceled();
  151. }
  152. });
  153. handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, handler);
  154. return tcs.Task;
  155. }
  156. /// <summary>Resolved address of the remote endpoint in URI format.</summary>
  157. public string ResolvedTarget
  158. {
  159. get
  160. {
  161. return handle.GetTarget();
  162. }
  163. }
  164. /// <summary>The original target used to create the channel.</summary>
  165. public string Target
  166. {
  167. get
  168. {
  169. return this.target;
  170. }
  171. }
  172. /// <summary>
  173. /// Returns a token that gets cancelled once <c>ShutdownAsync</c> is invoked.
  174. /// </summary>
  175. public CancellationToken ShutdownToken
  176. {
  177. get
  178. {
  179. return this.shutdownTokenSource.Token;
  180. }
  181. }
  182. /// <summary>
  183. /// Allows explicitly requesting channel to connect without starting an RPC.
  184. /// Returned task completes once state Ready was seen. If the deadline is reached,
  185. /// or channel enters the Shutdown state, the task is cancelled.
  186. /// There is no need to call this explicitly unless your use case requires that.
  187. /// Starting an RPC on a new channel will request connection implicitly.
  188. /// </summary>
  189. /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param>
  190. public async Task ConnectAsync(DateTime? deadline = null)
  191. {
  192. var currentState = GetConnectivityState(true);
  193. while (currentState != ChannelState.Ready)
  194. {
  195. if (currentState == ChannelState.Shutdown)
  196. {
  197. throw new OperationCanceledException("Channel has reached Shutdown state.");
  198. }
  199. await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);
  200. currentState = GetConnectivityState(false);
  201. }
  202. }
  203. /// <summary>
  204. /// Shuts down the channel cleanly. It is strongly recommended to shutdown
  205. /// all previously created channels before exiting from the process.
  206. /// </summary>
  207. /// <remarks>
  208. /// This method doesn't wait for all calls on this channel to finish (nor does
  209. /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure
  210. /// all the calls on this channel have finished (successfully or with an error)
  211. /// before shutting down the channel to ensure channel shutdown won't impact
  212. /// the outcome of those remote calls.
  213. /// </remarks>
  214. public async Task ShutdownAsync()
  215. {
  216. lock (myLock)
  217. {
  218. GrpcPreconditions.CheckState(!shutdownRequested);
  219. shutdownRequested = true;
  220. }
  221. GrpcEnvironment.UnregisterChannel(this);
  222. shutdownTokenSource.Cancel();
  223. var activeCallCount = activeCallCounter.Count;
  224. if (activeCallCount > 0)
  225. {
  226. Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
  227. }
  228. handle.Dispose();
  229. await Task.WhenAll(GrpcEnvironment.ReleaseAsync(), connectivityWatcherTask).ConfigureAwait(false);
  230. }
  231. internal ChannelSafeHandle Handle
  232. {
  233. get
  234. {
  235. return this.handle;
  236. }
  237. }
  238. internal GrpcEnvironment Environment
  239. {
  240. get
  241. {
  242. return this.environment;
  243. }
  244. }
  245. internal CompletionQueueSafeHandle CompletionQueue
  246. {
  247. get
  248. {
  249. return this.completionQueue;
  250. }
  251. }
  252. internal void AddCallReference(object call)
  253. {
  254. activeCallCounter.Increment();
  255. bool success = false;
  256. handle.DangerousAddRef(ref success);
  257. GrpcPreconditions.CheckState(success);
  258. }
  259. internal void RemoveCallReference(object call)
  260. {
  261. handle.DangerousRelease();
  262. activeCallCounter.Decrement();
  263. }
  264. private ChannelState GetConnectivityState(bool tryToConnect)
  265. {
  266. try
  267. {
  268. return handle.CheckConnectivityState(tryToConnect);
  269. }
  270. catch (ObjectDisposedException)
  271. {
  272. return ChannelState.Shutdown;
  273. }
  274. }
  275. /// <summary>
  276. /// Constantly Watches channel connectivity status to work around https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/822
  277. /// </summary>
  278. private async Task RunConnectivityWatcherAsync()
  279. {
  280. try
  281. {
  282. var lastState = State;
  283. while (lastState != ChannelState.Shutdown)
  284. {
  285. lock (myLock)
  286. {
  287. if (shutdownRequested)
  288. {
  289. break;
  290. }
  291. }
  292. try
  293. {
  294. await WaitForStateChangedAsync(lastState, DateTime.UtcNow.AddSeconds(1)).ConfigureAwait(false);
  295. }
  296. catch (TaskCanceledException)
  297. {
  298. // ignore timeout
  299. }
  300. lastState = State;
  301. }
  302. }
  303. catch (ObjectDisposedException) {
  304. // during shutdown, channel is going to be disposed.
  305. }
  306. }
  307. private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
  308. {
  309. var key = ChannelOptions.PrimaryUserAgentString;
  310. var userAgentString = "";
  311. ChannelOption option;
  312. if (options.TryGetValue(key, out option))
  313. {
  314. // user-provided userAgentString needs to be at the beginning
  315. userAgentString = option.StringValue + " ";
  316. };
  317. // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
  318. userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
  319. options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);
  320. }
  321. private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)
  322. {
  323. var dict = new Dictionary<string, ChannelOption>();
  324. if (options == null)
  325. {
  326. return dict;
  327. }
  328. foreach (var option in options)
  329. {
  330. dict.Add(option.Name, option);
  331. }
  332. return dict;
  333. }
  334. }
  335. }