Channel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #region Copyright notice and license
  2. // Copyright 2015 gRPC authors.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #endregion
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Threading;
  19. using System.Threading.Tasks;
  20. using Grpc.Core.Internal;
  21. using Grpc.Core.Logging;
  22. using Grpc.Core.Utils;
  23. namespace Grpc.Core
  24. {
  25. /// <summary>
  26. /// Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers.
  27. /// More client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking
  28. /// a remote call so in general you should reuse a single channel for as many calls as possible.
  29. /// </summary>
  30. public class Channel
  31. {
  32. static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
  33. readonly object myLock = new object();
  34. readonly AtomicCounter activeCallCounter = new AtomicCounter();
  35. readonly CancellationTokenSource shutdownTokenSource = new CancellationTokenSource();
  36. readonly string target;
  37. readonly GrpcEnvironment environment;
  38. readonly CompletionQueueSafeHandle completionQueue;
  39. readonly ChannelSafeHandle handle;
  40. readonly Dictionary<string, ChannelOption> options;
  41. bool shutdownRequested;
  42. /// <summary>
  43. /// Creates a channel that connects to a specific host.
  44. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  45. /// </summary>
  46. /// <param name="target">Target of the channel.</param>
  47. /// <param name="credentials">Credentials to secure the channel.</param>
  48. public Channel(string target, ChannelCredentials credentials) :
  49. this(target, credentials, null)
  50. {
  51. }
  52. /// <summary>
  53. /// Creates a channel that connects to a specific host.
  54. /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
  55. /// </summary>
  56. /// <param name="target">Target of the channel.</param>
  57. /// <param name="credentials">Credentials to secure the channel.</param>
  58. /// <param name="options">Channel options.</param>
  59. public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options)
  60. {
  61. this.target = GrpcPreconditions.CheckNotNull(target, "target");
  62. this.options = CreateOptionsDictionary(options);
  63. EnsureUserAgentChannelOption(this.options);
  64. this.environment = GrpcEnvironment.AddRef();
  65. this.completionQueue = this.environment.PickCompletionQueue();
  66. using (var nativeCredentials = credentials.ToNativeCredentials())
  67. using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
  68. {
  69. if (nativeCredentials != null)
  70. {
  71. this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
  72. }
  73. else
  74. {
  75. this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
  76. }
  77. }
  78. GrpcEnvironment.RegisterChannel(this);
  79. }
  80. /// <summary>
  81. /// Creates a channel that connects to a specific host and port.
  82. /// </summary>
  83. /// <param name="host">The name or IP address of the host.</param>
  84. /// <param name="port">The port.</param>
  85. /// <param name="credentials">Credentials to secure the channel.</param>
  86. public Channel(string host, int port, ChannelCredentials credentials) :
  87. this(host, port, credentials, null)
  88. {
  89. }
  90. /// <summary>
  91. /// Creates a channel that connects to a specific host and port.
  92. /// </summary>
  93. /// <param name="host">The name or IP address of the host.</param>
  94. /// <param name="port">The port.</param>
  95. /// <param name="credentials">Credentials to secure the channel.</param>
  96. /// <param name="options">Channel options.</param>
  97. public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options) :
  98. this(string.Format("{0}:{1}", host, port), credentials, options)
  99. {
  100. }
  101. /// <summary>
  102. /// Gets current connectivity state of this channel.
  103. /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned.
  104. /// </summary>
  105. public ChannelState State
  106. {
  107. get
  108. {
  109. return GetConnectivityState(false);
  110. }
  111. }
  112. // cached handler for watch connectivity state
  113. static readonly BatchCompletionDelegate WatchConnectivityStateHandler = (success, ctx, state) =>
  114. {
  115. var tcs = (TaskCompletionSource<bool>) state;
  116. tcs.SetResult(success);
  117. };
  118. /// <summary>
  119. /// Returned tasks completes once channel state has become different from
  120. /// given lastObservedState.
  121. /// If deadline is reached or and error occurs, returned task is cancelled.
  122. /// </summary>
  123. public async Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null)
  124. {
  125. var result = await WaitForStateChangedInternalAsync(lastObservedState, deadline).ConfigureAwait(false);
  126. if (!result)
  127. {
  128. throw new TaskCanceledException("Reached deadline.");
  129. }
  130. }
  131. /// <summary>
  132. /// Returned tasks completes once channel state has become different from
  133. /// given lastObservedState (<c>true</c> is returned) or if the wait has timed out (<c>false</c> is returned).
  134. /// </summary>
  135. internal Task<bool> WaitForStateChangedInternalAsync(ChannelState lastObservedState, DateTime? deadline = null)
  136. {
  137. GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown,
  138. "Shutdown is a terminal state. No further state changes can occur.");
  139. var tcs = new TaskCompletionSource<bool>();
  140. var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;
  141. lock (myLock)
  142. {
  143. if (handle.IsClosed)
  144. {
  145. // If channel has been already shutdown and handle was disposed, we would end up with
  146. // an abandoned completion added to the completion registry. Instead, we make sure we fail early.
  147. throw new ObjectDisposedException(nameof(handle), "Channel handle has already been disposed.");
  148. }
  149. else
  150. {
  151. // pass "tcs" as "state" for WatchConnectivityStateHandler.
  152. handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, WatchConnectivityStateHandler, tcs);
  153. }
  154. }
  155. return tcs.Task;
  156. }
  157. /// <summary>Resolved address of the remote endpoint in URI format.</summary>
  158. public string ResolvedTarget
  159. {
  160. get
  161. {
  162. return handle.GetTarget();
  163. }
  164. }
  165. /// <summary>The original target used to create the channel.</summary>
  166. public string Target
  167. {
  168. get
  169. {
  170. return this.target;
  171. }
  172. }
  173. /// <summary>
  174. /// Returns a token that gets cancelled once <c>ShutdownAsync</c> is invoked.
  175. /// </summary>
  176. public CancellationToken ShutdownToken
  177. {
  178. get
  179. {
  180. return this.shutdownTokenSource.Token;
  181. }
  182. }
  183. /// <summary>
  184. /// Allows explicitly requesting channel to connect without starting an RPC.
  185. /// Returned task completes once state Ready was seen. If the deadline is reached,
  186. /// or channel enters the Shutdown state, the task is cancelled.
  187. /// There is no need to call this explicitly unless your use case requires that.
  188. /// Starting an RPC on a new channel will request connection implicitly.
  189. /// </summary>
  190. /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param>
  191. public async Task ConnectAsync(DateTime? deadline = null)
  192. {
  193. var currentState = GetConnectivityState(true);
  194. while (currentState != ChannelState.Ready)
  195. {
  196. if (currentState == ChannelState.Shutdown)
  197. {
  198. throw new OperationCanceledException("Channel has reached Shutdown state.");
  199. }
  200. await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);
  201. currentState = GetConnectivityState(false);
  202. }
  203. }
  204. /// <summary>
  205. /// Shuts down the channel cleanly. It is strongly recommended to shutdown
  206. /// all previously created channels before exiting from the process.
  207. /// </summary>
  208. /// <remarks>
  209. /// This method doesn't wait for all calls on this channel to finish (nor does
  210. /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure
  211. /// all the calls on this channel have finished (successfully or with an error)
  212. /// before shutting down the channel to ensure channel shutdown won't impact
  213. /// the outcome of those remote calls.
  214. /// </remarks>
  215. public async Task ShutdownAsync()
  216. {
  217. lock (myLock)
  218. {
  219. GrpcPreconditions.CheckState(!shutdownRequested);
  220. shutdownRequested = true;
  221. }
  222. GrpcEnvironment.UnregisterChannel(this);
  223. shutdownTokenSource.Cancel();
  224. var activeCallCount = activeCallCounter.Count;
  225. if (activeCallCount > 0)
  226. {
  227. Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
  228. }
  229. lock (myLock)
  230. {
  231. handle.Dispose();
  232. }
  233. await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false);
  234. }
  235. internal ChannelSafeHandle Handle
  236. {
  237. get
  238. {
  239. return this.handle;
  240. }
  241. }
  242. internal GrpcEnvironment Environment
  243. {
  244. get
  245. {
  246. return this.environment;
  247. }
  248. }
  249. internal CompletionQueueSafeHandle CompletionQueue
  250. {
  251. get
  252. {
  253. return this.completionQueue;
  254. }
  255. }
  256. internal void AddCallReference(object call)
  257. {
  258. activeCallCounter.Increment();
  259. bool success = false;
  260. handle.DangerousAddRef(ref success);
  261. GrpcPreconditions.CheckState(success);
  262. }
  263. internal void RemoveCallReference(object call)
  264. {
  265. handle.DangerousRelease();
  266. activeCallCounter.Decrement();
  267. }
  268. private ChannelState GetConnectivityState(bool tryToConnect)
  269. {
  270. try
  271. {
  272. lock (myLock)
  273. {
  274. return handle.CheckConnectivityState(tryToConnect);
  275. }
  276. }
  277. catch (ObjectDisposedException)
  278. {
  279. return ChannelState.Shutdown;
  280. }
  281. }
  282. private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
  283. {
  284. var key = ChannelOptions.PrimaryUserAgentString;
  285. var userAgentString = "";
  286. ChannelOption option;
  287. if (options.TryGetValue(key, out option))
  288. {
  289. // user-provided userAgentString needs to be at the beginning
  290. userAgentString = option.StringValue + " ";
  291. };
  292. // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
  293. userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
  294. options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);
  295. }
  296. private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)
  297. {
  298. var dict = new Dictionary<string, ChannelOption>();
  299. if (options == null)
  300. {
  301. return dict;
  302. }
  303. foreach (var option in options)
  304. {
  305. dict.Add(option.Name, option);
  306. }
  307. return dict;
  308. }
  309. }
  310. }