blob: 4f29c35b3212a8fc4ac8f089e87da05ee095e41e [file] [log] [blame]
Jan Tattermuscha7fff862015-02-13 11:08:08 -08001#region Copyright notice and license
Craig Tiller6169d5f2016-03-31 07:46:18 -07002// Copyright 2015, Google Inc.
Jan Tattermuscha7fff862015-02-13 11:08:08 -08003// All rights reserved.
Craig Tiller190d3602015-02-18 09:23:38 -08004//
Jan Tattermuscha7fff862015-02-13 11:08:08 -08005// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
Craig Tiller190d3602015-02-18 09:23:38 -08008//
Jan Tattermuscha7fff862015-02-13 11:08:08 -08009// * 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.
Craig Tiller190d3602015-02-18 09:23:38 -080018//
Jan Tattermuscha7fff862015-02-13 11:08:08 -080019// 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.
Jan Tattermuscha7fff862015-02-13 11:08:08 -080030#endregion
Jan Tattermusch766d72b2015-07-21 20:09:25 -070031
Jan Tattermuscha7608b02015-02-03 17:54:38 -080032using System;
Jan Tattermusch2ddb5a62015-06-08 17:51:36 -070033using System.Collections.Generic;
Jan Tattermusch528fb662016-05-12 08:38:41 -070034using System.Threading;
Jan Tattermuscha7608b02015-02-03 17:54:38 -080035using System.Threading.Tasks;
Jan Tattermusch766d72b2015-07-21 20:09:25 -070036
Jan Tattermusch30868622015-02-19 09:22:33 -080037using Grpc.Core.Internal;
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -070038using Grpc.Core.Logging;
39using Grpc.Core.Utils;
Jan Tattermuscha7608b02015-02-03 17:54:38 -080040
Jan Tattermusch30868622015-02-19 09:22:33 -080041namespace Grpc.Core
Jan Tattermuscha7608b02015-02-03 17:54:38 -080042{
Jan Tattermusch286975f2015-03-12 14:04:36 -070043 /// <summary>
Jan Tattermusch12855fc2015-08-24 16:43:23 -070044 /// Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers.
45 /// More client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking
46 /// a remote call so in general you should reuse a single channel for as many calls as possible.
Jan Tattermusch286975f2015-03-12 14:04:36 -070047 /// </summary>
Jan Tattermusch2b357952015-08-20 14:54:33 -070048 public class Channel
Jan Tattermusch15329232015-03-02 15:32:47 -080049 {
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -070050 static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
51
Jan Tattermusch2b357952015-08-20 14:54:33 -070052 readonly object myLock = new object();
53 readonly AtomicCounter activeCallCounter = new AtomicCounter();
Jan Tattermusch528fb662016-05-12 08:38:41 -070054 readonly CancellationTokenSource shutdownTokenSource = new CancellationTokenSource();
Jan Tattermusch2b357952015-08-20 14:54:33 -070055
Jan Tattermusch0c140a82015-08-02 00:54:02 -070056 readonly string target;
Jan Tattermusch04eb89c2015-06-12 13:03:05 -070057 readonly GrpcEnvironment environment;
Jan Tattermusch5ee8e772016-05-24 16:17:10 -040058 readonly CompletionQueueSafeHandle completionQueue;
Jan Tattermuscha7608b02015-02-03 17:54:38 -080059 readonly ChannelSafeHandle handle;
Jan Tattermusch7ebbc472015-12-08 22:39:02 -080060 readonly Dictionary<string, ChannelOption> options;
Jan Tattermusch2b357952015-08-20 14:54:33 -070061
62 bool shutdownRequested;
Jan Tattermuscha7608b02015-02-03 17:54:38 -080063
Jan Tattermusch15329232015-03-02 15:32:47 -080064 /// <summary>
Jan Tattermuschda71a4d2015-06-08 15:36:53 -070065 /// Creates a channel that connects to a specific host.
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -070066 /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
Jan Tattermusch15329232015-03-02 15:32:47 -080067 /// </summary>
Jan Tattermusch0c140a82015-08-02 00:54:02 -070068 /// <param name="target">Target of the channel.</param>
Jan Tattermuscha96ac052015-07-24 14:49:30 -070069 /// <param name="credentials">Credentials to secure the channel.</param>
Jan Tattermusch8d829d02016-06-06 16:43:54 -070070 public Channel(string target, ChannelCredentials credentials) :
71 this(target, credentials, null)
72 {
73 }
74
75 /// <summary>
76 /// Creates a channel that connects to a specific host.
77 /// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
78 /// </summary>
79 /// <param name="target">Target of the channel.</param>
80 /// <param name="credentials">Credentials to secure the channel.</param>
Jan Tattermusch2ddb5a62015-06-08 17:51:36 -070081 /// <param name="options">Channel options.</param>
Jan Tattermusch8d829d02016-06-06 16:43:54 -070082 public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options)
Jan Tattermusch15329232015-03-02 15:32:47 -080083 {
Jan Tattermusch7a3ee6a2016-02-18 10:36:02 -080084 this.target = GrpcPreconditions.CheckNotNull(target, "target");
Jan Tattermusch7ebbc472015-12-08 22:39:02 -080085 this.options = CreateOptionsDictionary(options);
Jan Tattermusch766d72b2015-07-21 20:09:25 -070086 EnsureUserAgentChannelOption(this.options);
Jan Tattermusch7ebbc472015-12-08 22:39:02 -080087 this.environment = GrpcEnvironment.AddRef();
88
Jan Tattermusch5ee8e772016-05-24 16:17:10 -040089 this.completionQueue = this.environment.PickCompletionQueue();
Jan Tattermusch08dea322015-10-26 17:34:10 -070090 using (var nativeCredentials = credentials.ToNativeCredentials())
Jan Tattermusch7ebbc472015-12-08 22:39:02 -080091 using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
Jan Tattermusch15329232015-03-02 15:32:47 -080092 {
Jan Tattermuscha96ac052015-07-24 14:49:30 -070093 if (nativeCredentials != null)
Jan Tattermusch15329232015-03-02 15:32:47 -080094 {
Jan Tattermusch0c140a82015-08-02 00:54:02 -070095 this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
Jan Tattermusch15329232015-03-02 15:32:47 -080096 }
97 else
98 {
Jan Tattermusch0c140a82015-08-02 00:54:02 -070099 this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
Jan Tattermusch15329232015-03-02 15:32:47 -0800100 }
101 }
Jan Tattermusch4aea5282016-06-01 12:42:54 -0700102 GrpcEnvironment.RegisterChannel(this);
Jan Tattermusch15329232015-03-02 15:32:47 -0800103 }
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800104
Jan Tattermuschda71a4d2015-06-08 15:36:53 -0700105 /// <summary>
106 /// Creates a channel that connects to a specific host and port.
107 /// </summary>
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700108 /// <param name="host">The name or IP address of the host.</param>
109 /// <param name="port">The port.</param>
Jan Tattermuscha96ac052015-07-24 14:49:30 -0700110 /// <param name="credentials">Credentials to secure the channel.</param>
Jan Tattermusch8d829d02016-06-06 16:43:54 -0700111 public Channel(string host, int port, ChannelCredentials credentials) :
112 this(host, port, credentials, null)
113 {
114 }
115
116 /// <summary>
117 /// Creates a channel that connects to a specific host and port.
118 /// </summary>
119 /// <param name="host">The name or IP address of the host.</param>
120 /// <param name="port">The port.</param>
121 /// <param name="credentials">Credentials to secure the channel.</param>
Jan Tattermusch2ddb5a62015-06-08 17:51:36 -0700122 /// <param name="options">Channel options.</param>
Jan Tattermusch8d829d02016-06-06 16:43:54 -0700123 public Channel(string host, int port, ChannelCredentials credentials, IEnumerable<ChannelOption> options) :
Jan Tattermusch2ddb5a62015-06-08 17:51:36 -0700124 this(string.Format("{0}:{1}", host, port), credentials, options)
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800125 {
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800126 }
127
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700128 /// <summary>
129 /// Gets current connectivity state of this channel.
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700130 /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned.
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700131 /// </summary>
132 public ChannelState State
133 {
134 get
135 {
Jan Tattermusch528fb662016-05-12 08:38:41 -0700136 return GetConnectivityState(false);
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700137 }
138 }
139
140 /// <summary>
141 /// Returned tasks completes once channel state has become different from
142 /// given lastObservedState.
143 /// If deadline is reached or and error occurs, returned task is cancelled.
144 /// </summary>
145 public Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null)
146 {
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700147 GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown,
148 "Shutdown is a terminal state. No further state changes can occur.");
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700149 var tcs = new TaskCompletionSource<object>();
150 var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture;
151 var handler = new BatchCompletionDelegate((success, ctx) =>
152 {
153 if (success)
154 {
155 tcs.SetResult(null);
156 }
157 else
158 {
159 tcs.SetCanceled();
160 }
161 });
Jan Tattermusche6d1de62016-05-25 19:32:15 -0400162 handle.WatchConnectivityState(lastObservedState, deadlineTimespec, completionQueue, handler);
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700163 return tcs.Task;
164 }
165
Jan Tattermusch0c140a82015-08-02 00:54:02 -0700166 /// <summary>Resolved address of the remote endpoint in URI format.</summary>
167 public string ResolvedTarget
Jan Tattermuschdead9052015-08-01 21:34:31 -0700168 {
169 get
170 {
171 return handle.GetTarget();
172 }
173 }
174
Jan Tattermusch0c140a82015-08-02 00:54:02 -0700175 /// <summary>The original target used to create the channel.</summary>
176 public string Target
177 {
178 get
179 {
180 return this.target;
181 }
182 }
183
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700184 /// <summary>
Jan Tattermusch528fb662016-05-12 08:38:41 -0700185 /// Returns a token that gets cancelled once <c>ShutdownAsync</c> is invoked.
186 /// </summary>
187 public CancellationToken ShutdownToken
188 {
189 get
190 {
191 return this.shutdownTokenSource.Token;
192 }
193 }
194
195 /// <summary>
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700196 /// Allows explicitly requesting channel to connect without starting an RPC.
197 /// Returned task completes once state Ready was seen. If the deadline is reached,
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700198 /// or channel enters the Shutdown state, the task is cancelled.
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700199 /// There is no need to call this explicitly unless your use case requires that.
200 /// Starting an RPC on a new channel will request connection implicitly.
201 /// </summary>
Jan Tattermusch12855fc2015-08-24 16:43:23 -0700202 /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param>
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700203 public async Task ConnectAsync(DateTime? deadline = null)
204 {
Jan Tattermusch528fb662016-05-12 08:38:41 -0700205 var currentState = GetConnectivityState(true);
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700206 while (currentState != ChannelState.Ready)
207 {
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700208 if (currentState == ChannelState.Shutdown)
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700209 {
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700210 throw new OperationCanceledException("Channel has reached Shutdown state.");
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700211 }
Jan Tattermusch723c34b2015-12-07 08:02:01 -0800212 await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);
Jan Tattermusch528fb662016-05-12 08:38:41 -0700213 currentState = GetConnectivityState(false);
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700214 }
215 }
216
217 /// <summary>
Jan Tattermuscha134fa72016-06-03 17:24:50 -0700218 /// Shuts down the channel cleanly. It is strongly recommended to shutdown
219 /// all previously created channels before exiting from the process.
Jan Tattermuschd8bbdea2015-07-22 12:51:06 -0700220 /// </summary>
Jan Tattermuscha134fa72016-06-03 17:24:50 -0700221 /// <remarks>
222 /// This method doesn't wait for all calls on this channel to finish (nor does
223 /// it explicitly cancel all outstanding calls). It is user's responsibility to make sure
224 /// all the calls on this channel have finished (successfully or with an error)
225 /// before shutting down the channel to ensure channel shutdown won't impact
226 /// the outcome of those remote calls.
227 /// </remarks>
Jan Tattermusch2b357952015-08-20 14:54:33 -0700228 public async Task ShutdownAsync()
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800229 {
Jan Tattermusch2b357952015-08-20 14:54:33 -0700230 lock (myLock)
231 {
Jan Tattermusch7a3ee6a2016-02-18 10:36:02 -0800232 GrpcPreconditions.CheckState(!shutdownRequested);
Jan Tattermusch2b357952015-08-20 14:54:33 -0700233 shutdownRequested = true;
234 }
Jan Tattermusch4aea5282016-06-01 12:42:54 -0700235 GrpcEnvironment.UnregisterChannel(this);
Jan Tattermusch2b357952015-08-20 14:54:33 -0700236
Jan Tattermusch528fb662016-05-12 08:38:41 -0700237 shutdownTokenSource.Cancel();
238
Jan Tattermusch2b357952015-08-20 14:54:33 -0700239 var activeCallCount = activeCallCounter.Count;
240 if (activeCallCount > 0)
241 {
242 Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
243 }
244
245 handle.Dispose();
246
Jan Tattermusch58584412016-05-31 14:32:27 -0700247 await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false);
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800248 }
249
Jan Tattermuscha5272b62015-04-30 11:56:46 -0700250 internal ChannelSafeHandle Handle
251 {
252 get
253 {
254 return this.handle;
255 }
256 }
257
Jan Tattermusch04eb89c2015-06-12 13:03:05 -0700258 internal GrpcEnvironment Environment
259 {
260 get
261 {
262 return this.environment;
263 }
264 }
265
Jan Tattermusch5ee8e772016-05-24 16:17:10 -0400266 internal CompletionQueueSafeHandle CompletionQueue
267 {
268 get
269 {
270 return this.completionQueue;
271 }
272 }
273
Jan Tattermusch2b357952015-08-20 14:54:33 -0700274 internal void AddCallReference(object call)
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800275 {
Jan Tattermusch2b357952015-08-20 14:54:33 -0700276 activeCallCounter.Increment();
277
278 bool success = false;
279 handle.DangerousAddRef(ref success);
Jan Tattermusch7a3ee6a2016-02-18 10:36:02 -0800280 GrpcPreconditions.CheckState(success);
Jan Tattermusch2b357952015-08-20 14:54:33 -0700281 }
282
283 internal void RemoveCallReference(object call)
284 {
285 handle.DangerousRelease();
286
287 activeCallCounter.Decrement();
Jan Tattermuscha7608b02015-02-03 17:54:38 -0800288 }
Jan Tattermusch15329232015-03-02 15:32:47 -0800289
Jan Tattermusch528fb662016-05-12 08:38:41 -0700290 private ChannelState GetConnectivityState(bool tryToConnect)
291 {
292 try
293 {
294 return handle.CheckConnectivityState(tryToConnect);
295 }
296 catch (ObjectDisposedException)
297 {
Jan Tattermusch49fb84a2016-06-03 16:34:48 -0700298 return ChannelState.Shutdown;
Jan Tattermusch528fb662016-05-12 08:38:41 -0700299 }
300 }
301
Jan Tattermusch7ebbc472015-12-08 22:39:02 -0800302 private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
Jan Tattermusch766d72b2015-07-21 20:09:25 -0700303 {
Jan Tattermusch7ebbc472015-12-08 22:39:02 -0800304 var key = ChannelOptions.PrimaryUserAgentString;
305 var userAgentString = "";
306
307 ChannelOption option;
308 if (options.TryGetValue(key, out option))
Jan Tattermusch766d72b2015-07-21 20:09:25 -0700309 {
Jan Tattermusch7ebbc472015-12-08 22:39:02 -0800310 // user-provided userAgentString needs to be at the beginning
311 userAgentString = option.StringValue + " ";
312 };
313
314 // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
315 userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
316
317 options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);
Jan Tattermusch766d72b2015-07-21 20:09:25 -0700318 }
319
Jan Tattermusch7ebbc472015-12-08 22:39:02 -0800320 private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)
Jan Tattermusch766d72b2015-07-21 20:09:25 -0700321 {
Jan Tattermusch7ebbc472015-12-08 22:39:02 -0800322 var dict = new Dictionary<string, ChannelOption>();
323 if (options == null)
324 {
325 return dict;
326 }
327 foreach (var option in options)
328 {
329 dict.Add(option.Name, option);
330 }
331 return dict;
Jan Tattermusch766d72b2015-07-21 20:09:25 -0700332 }
Jan Tattermusch15329232015-03-02 15:32:47 -0800333 }
Craig Tiller190d3602015-02-18 09:23:38 -0800334}