diff options
author | 2016-06-08 08:06:53 -0700 | |
---|---|---|
committer | 2016-06-08 08:06:53 -0700 | |
commit | da901c2a841ce2b60a84bf44c0ed994495fd5f3c (patch) | |
tree | e6669b226fbbdad8a7c820d82d91fb461bfd5e6b /src/csharp/Grpc.Core | |
parent | 6ae6fc7a797d1629fb8ceba4c5b8792f7c24c4a3 (diff) | |
parent | 2c57371b937e6ac37b2d331a817bb7f1e33b5383 (diff) |
Merge github.com:grpc/grpc into fuzz-me
Diffstat (limited to 'src/csharp/Grpc.Core')
-rw-r--r-- | src/csharp/Grpc.Core/AsyncClientStreamingCall.cs | 4 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs | 4 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/AsyncServerStreamingCall.cs | 4 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/AsyncUnaryCall.cs | 4 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/CallOptions.cs | 8 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Channel.cs | 55 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/ChannelState.cs | 2 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/GrpcEnvironment.cs | 121 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs | 28 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Server.cs | 99 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/ServerServiceDefinition.cs | 10 |
11 files changed, 279 insertions, 60 deletions
diff --git a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs index 5646fed3d9..02b08d2a10 100644 --- a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs @@ -127,6 +127,10 @@ namespace Grpc.Core /// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call. /// As a result, all resources being used by the call should be released eventually. /// </summary> + /// <remarks> + /// Normally, there is no need for you to dispose the call unless you want to utilize the + /// "Cancel" semantics of invoking <c>Dispose</c>. + /// </remarks> public void Dispose() { disposeAction.Invoke(); diff --git a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs index e75108c7e5..68fd6d0b05 100644 --- a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs @@ -117,6 +117,10 @@ namespace Grpc.Core /// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call. /// As a result, all resources being used by the call should be released eventually. /// </summary> + /// <remarks> + /// Normally, there is no need for you to dispose the call unless you want to utilize the + /// "Cancel" semantics of invoking <c>Dispose</c>. + /// </remarks> public void Dispose() { disposeAction.Invoke(); diff --git a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs index f953091984..5777c72615 100644 --- a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs @@ -103,6 +103,10 @@ namespace Grpc.Core /// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call. /// As a result, all resources being used by the call should be released eventually. /// </summary> + /// <remarks> + /// Normally, there is no need for you to dispose the call unless you want to utilize the + /// "Cancel" semantics of invoking <c>Dispose</c>. + /// </remarks> public void Dispose() { disposeAction.Invoke(); diff --git a/src/csharp/Grpc.Core/AsyncUnaryCall.cs b/src/csharp/Grpc.Core/AsyncUnaryCall.cs index 97df8f5e91..d180c27922 100644 --- a/src/csharp/Grpc.Core/AsyncUnaryCall.cs +++ b/src/csharp/Grpc.Core/AsyncUnaryCall.cs @@ -112,6 +112,10 @@ namespace Grpc.Core /// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call. /// As a result, all resources being used by the call should be released eventually. /// </summary> + /// <remarks> + /// Normally, there is no need for you to dispose the call unless you want to utilize the + /// "Cancel" semantics of invoking <c>Dispose</c>. + /// </remarks> public void Dispose() { disposeAction.Invoke(); diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs index 9ca88849ee..35548cfc96 100644 --- a/src/csharp/Grpc.Core/CallOptions.cs +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -88,7 +88,13 @@ namespace Grpc.Core } /// <summary> - /// Token that can be used for cancelling the call. + /// Token that can be used for cancelling the call on the client side. + /// Cancelling the token will request cancellation + /// of the remote call. Best effort will be made to deliver the cancellation + /// notification to the server and interaction of the call with the server side + /// will be terminated. Unless the call finishes before the cancellation could + /// happen (there is an inherent race), + /// the call will finish with <c>StatusCode.Cancelled</c> status. /// </summary> public CancellationToken CancellationToken { diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index 886adfec33..4f29c35b32 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -67,8 +67,19 @@ namespace Grpc.Core /// </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 = null) + public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options) { this.target = GrpcPreconditions.CheckNotNull(target, "target"); this.options = CreateOptionsDictionary(options); @@ -88,6 +99,18 @@ namespace Grpc.Core this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs); } } + 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> @@ -97,14 +120,14 @@ namespace Grpc.Core /// <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 = null) : + 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.FatalFailure</c> will be returned. + /// After channel is has been shutdown, <c>ChannelState.Shutdown</c> will be returned. /// </summary> public ChannelState State { @@ -121,8 +144,8 @@ namespace Grpc.Core /// </summary> public Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null) { - GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.FatalFailure, - "FatalFailure is a terminal state. No further state changes can occur."); + GrpcPreconditions.CheckArgument(lastObservedState != ChannelState.Shutdown, + "Shutdown is a terminal state. No further state changes can occur."); var tcs = new TaskCompletionSource<object>(); var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture; var handler = new BatchCompletionDelegate((success, ctx) => @@ -172,7 +195,7 @@ namespace Grpc.Core /// <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 FatalFailure state, the task is cancelled. + /// 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> @@ -182,9 +205,9 @@ namespace Grpc.Core var currentState = GetConnectivityState(true); while (currentState != ChannelState.Ready) { - if (currentState == ChannelState.FatalFailure) + if (currentState == ChannelState.Shutdown) { - throw new OperationCanceledException("Channel has reached FatalFailure state."); + throw new OperationCanceledException("Channel has reached Shutdown state."); } await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false); currentState = GetConnectivityState(false); @@ -192,9 +215,16 @@ namespace Grpc.Core } /// <summary> - /// Waits until there are no more active calls for this channel and then cleans up - /// resources used by this channel. + /// 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) @@ -202,6 +232,7 @@ namespace Grpc.Core GrpcPreconditions.CheckState(!shutdownRequested); shutdownRequested = true; } + GrpcEnvironment.UnregisterChannel(this); shutdownTokenSource.Cancel(); @@ -213,7 +244,7 @@ namespace Grpc.Core handle.Dispose(); - await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false); + await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false); } internal ChannelSafeHandle Handle @@ -264,7 +295,7 @@ namespace Grpc.Core } catch (ObjectDisposedException) { - return ChannelState.FatalFailure; + return ChannelState.Shutdown; } } diff --git a/src/csharp/Grpc.Core/ChannelState.cs b/src/csharp/Grpc.Core/ChannelState.cs index d293b98f75..a6c3b2a488 100644 --- a/src/csharp/Grpc.Core/ChannelState.cs +++ b/src/csharp/Grpc.Core/ChannelState.cs @@ -64,6 +64,6 @@ namespace Grpc.Core /// <summary> /// Channel has seen a failure that it cannot recover from /// </summary> - FatalFailure + Shutdown } } diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs index 18af1099f1..0359d9092a 100644 --- a/src/csharp/Grpc.Core/GrpcEnvironment.cs +++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs @@ -35,6 +35,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Grpc.Core.Internal; using Grpc.Core.Logging; using Grpc.Core.Utils; @@ -53,12 +54,16 @@ namespace Grpc.Core static int refCount; static int? customThreadPoolSize; static int? customCompletionQueueCount; + static readonly HashSet<Channel> registeredChannels = new HashSet<Channel>(); + static readonly HashSet<Server> registeredServers = new HashSet<Server>(); static ILogger logger = new ConsoleLogger(); + readonly object myLock = new object(); readonly GrpcThreadPool threadPool; readonly DebugStats debugStats = new DebugStats(); readonly AtomicCounter cqPickerCounter = new AtomicCounter(); + bool isClosed; /// <summary> @@ -67,6 +72,8 @@ namespace Grpc.Core /// </summary> internal static GrpcEnvironment AddRef() { + ShutdownHooks.Register(); + lock (staticLock) { refCount++; @@ -79,21 +86,26 @@ namespace Grpc.Core } /// <summary> - /// Decrements the reference count for currently active environment and shuts down the gRPC environment if reference count drops to zero. - /// (and blocks until the environment has been fully shutdown). + /// Decrements the reference count for currently active environment and asynchronously shuts down the gRPC environment if reference count drops to zero. /// </summary> - internal static void Release() + internal static async Task ReleaseAsync() { + GrpcEnvironment instanceToShutdown = null; lock (staticLock) { GrpcPreconditions.CheckState(refCount > 0); refCount--; if (refCount == 0) { - instance.Close(); + instanceToShutdown = instance; instance = null; } } + + if (instanceToShutdown != null) + { + await instanceToShutdown.ShutdownAsync(); + } } internal static int GetRefCount() @@ -104,6 +116,68 @@ namespace Grpc.Core } } + internal static void RegisterChannel(Channel channel) + { + lock (staticLock) + { + GrpcPreconditions.CheckNotNull(channel); + registeredChannels.Add(channel); + } + } + + internal static void UnregisterChannel(Channel channel) + { + lock (staticLock) + { + GrpcPreconditions.CheckNotNull(channel); + GrpcPreconditions.CheckArgument(registeredChannels.Remove(channel), "Channel not found in the registered channels set."); + } + } + + internal static void RegisterServer(Server server) + { + lock (staticLock) + { + GrpcPreconditions.CheckNotNull(server); + registeredServers.Add(server); + } + } + + internal static void UnregisterServer(Server server) + { + lock (staticLock) + { + GrpcPreconditions.CheckNotNull(server); + GrpcPreconditions.CheckArgument(registeredServers.Remove(server), "Server not found in the registered servers set."); + } + } + + /// <summary> + /// Requests shutdown of all channels created by the current process. + /// </summary> + public static Task ShutdownChannelsAsync() + { + HashSet<Channel> snapshot = null; + lock (staticLock) + { + snapshot = new HashSet<Channel>(registeredChannels); + } + return Task.WhenAll(snapshot.Select((channel) => channel.ShutdownAsync())); + } + + /// <summary> + /// Requests immediate shutdown of all servers created by the current process. + /// </summary> + public static Task KillServersAsync() + { + HashSet<Server> snapshot = null; + lock (staticLock) + { + snapshot = new HashSet<Server>(registeredServers); + } + return Task.WhenAll(snapshot.Select((server) => server.KillAsync())); + } + /// <summary> /// Gets application-wide logger used by gRPC. /// </summary> @@ -180,6 +254,14 @@ namespace Grpc.Core } } + internal bool IsAlive + { + get + { + return this.threadPool.IsAlive; + } + } + /// <summary> /// Picks a completion queue in a round-robin fashion. /// Shouldn't be invoked on a per-call basis (used at per-channel basis). @@ -223,13 +305,13 @@ namespace Grpc.Core /// <summary> /// Shuts down this environment. /// </summary> - private void Close() + private async Task ShutdownAsync() { if (isClosed) { throw new InvalidOperationException("Close has already been called"); } - threadPool.Stop(); + await threadPool.StopAsync().ConfigureAwait(false); GrpcNativeShutdown(); isClosed = true; @@ -257,5 +339,32 @@ namespace Grpc.Core // by default, create a completion queue for each thread return GetThreadPoolSizeOrDefault(); } + + private static class ShutdownHooks + { + static object staticLock = new object(); + static bool hooksRegistered; + + public static void Register() + { + lock (staticLock) + { + if (!hooksRegistered) + { + AppDomain.CurrentDomain.ProcessExit += ShutdownHookHandler; + AppDomain.CurrentDomain.DomainUnload += ShutdownHookHandler; + } + hooksRegistered = true; + } + } + + /// <summary> + /// Handler for AppDomain.DomainUnload and AppDomain.ProcessExit hooks. + /// </summary> + private static void ShutdownHookHandler(object sender, EventArgs e) + { + Task.WaitAll(GrpcEnvironment.ShutdownChannelsAsync(), GrpcEnvironment.KillServersAsync()); + } + } } } diff --git a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs index 4de543bef7..a446c1f99f 100644 --- a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs +++ b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs @@ -35,6 +35,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Grpc.Core.Logging; using Grpc.Core.Utils; @@ -53,6 +54,8 @@ namespace Grpc.Core.Internal readonly int poolSize; readonly int completionQueueCount; + bool stopRequested; + IReadOnlyCollection<CompletionQueueSafeHandle> completionQueues; /// <summary> @@ -84,15 +87,21 @@ namespace Grpc.Core.Internal } } - public void Stop() + public Task StopAsync() { lock (myLock) { + GrpcPreconditions.CheckState(!stopRequested, "Stop already requested."); + stopRequested = true; + foreach (var cq in completionQueues) { cq.Shutdown(); } + } + return Task.Run(() => + { foreach (var thread in threads) { thread.Join(); @@ -102,6 +111,21 @@ namespace Grpc.Core.Internal { cq.Dispose(); } + }); + } + + /// <summary> + /// Returns true if there is at least one thread pool thread that hasn't + /// already stopped. + /// Threads can either stop because all completion queues shut down or + /// because all foreground threads have already shutdown and process is + /// going to exit. + /// </summary> + internal bool IsAlive + { + get + { + return threads.Any(t => t.ThreadState != ThreadState.Stopped); } } @@ -119,7 +143,7 @@ namespace Grpc.Core.Internal var cq = completionQueues.ElementAt(cqIndex); var thread = new Thread(new ThreadStart(() => RunHandlerLoop(cq))); - thread.IsBackground = false; + thread.IsBackground = true; thread.Name = string.Format("grpc {0} (cq {1})", threadIndex, cqIndex); thread.Start(); diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs index 069185e13a..ae7a8c9a9a 100644 --- a/src/csharp/Grpc.Core/Server.cs +++ b/src/csharp/Grpc.Core/Server.cs @@ -67,11 +67,19 @@ namespace Grpc.Core bool startRequested; volatile bool shutdownRequested; + + /// <summary> + /// Creates a new server. + /// </summary> + public Server() : this(null) + { + } + /// <summary> - /// Create a new server. + /// Creates a new server. /// </summary> /// <param name="options">Channel options.</param> - public Server(IEnumerable<ChannelOption> options = null) + public Server(IEnumerable<ChannelOption> options) { this.serviceDefinitions = new ServiceDefinitionCollection(this); this.ports = new ServerPortCollection(this); @@ -86,6 +94,7 @@ namespace Grpc.Core { this.handle.RegisterCompletionQueue(cq); } + GrpcEnvironment.RegisterServer(this); } /// <summary> @@ -152,43 +161,24 @@ namespace Grpc.Core /// cleans up used resources. The returned task finishes when shutdown procedure /// is complete. /// </summary> - public async Task ShutdownAsync() + /// <remarks> + /// It is strongly recommended to shutdown all previously created servers before exiting from the process. + /// </remarks> + public Task ShutdownAsync() { - lock (myLock) - { - GrpcPreconditions.CheckState(startRequested); - GrpcPreconditions.CheckState(!shutdownRequested); - shutdownRequested = true; - } - - var cq = environment.CompletionQueues.First(); // any cq will do - handle.ShutdownAndNotify(HandleServerShutdown, cq); - await shutdownTcs.Task.ConfigureAwait(false); - DisposeHandle(); - - await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false); + return ShutdownInternalAsync(false); } /// <summary> /// Requests server shutdown while cancelling all the in-progress calls. /// The returned task finishes when shutdown procedure is complete. /// </summary> - public async Task KillAsync() + /// <remarks> + /// It is strongly recommended to shutdown all previously created servers before exiting from the process. + /// </remarks> + public Task KillAsync() { - lock (myLock) - { - GrpcPreconditions.CheckState(startRequested); - GrpcPreconditions.CheckState(!shutdownRequested); - shutdownRequested = true; - } - - var cq = environment.CompletionQueues.First(); // any cq will do - handle.ShutdownAndNotify(HandleServerShutdown, cq); - handle.CancelAllCalls(); - await shutdownTcs.Task.ConfigureAwait(false); - DisposeHandle(); - - await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false); + return ShutdownInternalAsync(true); } internal void AddCallReference(object call) @@ -207,6 +197,53 @@ namespace Grpc.Core } /// <summary> + /// Shuts down the server. + /// </summary> + private async Task ShutdownInternalAsync(bool kill) + { + lock (myLock) + { + GrpcPreconditions.CheckState(startRequested); + GrpcPreconditions.CheckState(!shutdownRequested); + shutdownRequested = true; + } + GrpcEnvironment.UnregisterServer(this); + + var cq = environment.CompletionQueues.First(); // any cq will do + handle.ShutdownAndNotify(HandleServerShutdown, cq); + if (kill) + { + handle.CancelAllCalls(); + } + + await ShutdownCompleteOrEnvironmentDeadAsync().ConfigureAwait(false); + + DisposeHandle(); + + await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false); + } + + /// <summary> + /// In case the environment's threadpool becomes dead, the shutdown completion will + /// never be delivered, but we need to release the environment's handle anyway. + /// </summary> + private async Task ShutdownCompleteOrEnvironmentDeadAsync() + { + while (true) + { + var task = await Task.WhenAny(shutdownTcs.Task, Task.Delay(20)).ConfigureAwait(false); + if (shutdownTcs.Task == task) + { + return; + } + if (!environment.IsAlive) + { + return; + } + } + } + + /// <summary> /// Adds a service definition. /// </summary> private void AddServiceDefinitionInternal(ServerServiceDefinition serviceDefinition) diff --git a/src/csharp/Grpc.Core/ServerServiceDefinition.cs b/src/csharp/Grpc.Core/ServerServiceDefinition.cs index deb1431ca3..ac08c04bf6 100644 --- a/src/csharp/Grpc.Core/ServerServiceDefinition.cs +++ b/src/csharp/Grpc.Core/ServerServiceDefinition.cs @@ -63,11 +63,10 @@ namespace Grpc.Core /// <summary> /// Creates a new builder object for <c>ServerServiceDefinition</c>. /// </summary> - /// <param name="serviceName">The service name.</param> /// <returns>The builder object.</returns> - public static Builder CreateBuilder(string serviceName) + public static Builder CreateBuilder() { - return new Builder(serviceName); + return new Builder(); } /// <summary> @@ -75,16 +74,13 @@ namespace Grpc.Core /// </summary> public class Builder { - readonly string serviceName; readonly Dictionary<string, IServerCallHandler> callHandlers = new Dictionary<string, IServerCallHandler>(); /// <summary> /// Creates a new instance of builder. /// </summary> - /// <param name="serviceName">The service name.</param> - public Builder(string serviceName) + public Builder() { - this.serviceName = serviceName; } /// <summary> |