diff options
Diffstat (limited to 'src/csharp/Grpc.Core')
42 files changed, 790 insertions, 271 deletions
diff --git a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs index bf020cd627..5646fed3d9 100644 --- a/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncClientStreamingCall.cs @@ -40,18 +40,22 @@ namespace Grpc.Core /// <summary> /// Return type for client streaming calls. /// </summary> + /// <typeparam name="TRequest">Request message type for this call.</typeparam> + /// <typeparam name="TResponse">Response message type for this call.</typeparam> public sealed class AsyncClientStreamingCall<TRequest, TResponse> : IDisposable { readonly IClientStreamWriter<TRequest> requestStream; readonly Task<TResponse> responseAsync; + readonly Task<Metadata> responseHeadersAsync; readonly Func<Status> getStatusFunc; readonly Func<Metadata> getTrailersFunc; readonly Action disposeAction; - public AsyncClientStreamingCall(IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) + internal AsyncClientStreamingCall(IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) { this.requestStream = requestStream; this.responseAsync = responseAsync; + this.responseHeadersAsync = responseHeadersAsync; this.getStatusFunc = getStatusFunc; this.getTrailersFunc = getTrailersFunc; this.disposeAction = disposeAction; @@ -69,6 +73,17 @@ namespace Grpc.Core } /// <summary> + /// Asynchronous access to response headers. + /// </summary> + public Task<Metadata> ResponseHeadersAsync + { + get + { + return this.responseHeadersAsync; + } + } + + /// <summary> /// Async stream to send streaming requests. /// </summary> public IClientStreamWriter<TRequest> RequestStream @@ -89,6 +104,24 @@ namespace Grpc.Core } /// <summary> + /// Gets the call status if the call has already finished. + /// Throws InvalidOperationException otherwise. + /// </summary> + public Status GetStatus() + { + return getStatusFunc(); + } + + /// <summary> + /// Gets the call trailing metadata if the call has already finished. + /// Throws InvalidOperationException otherwise. + /// </summary> + public Metadata GetTrailers() + { + return getTrailersFunc(); + } + + /// <summary> /// Provides means to cleanup after the call. /// If the call has already finished normally (request stream has been completed and call result has been received), doesn't do anything. /// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call. diff --git a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs index 0979de606f..e75108c7e5 100644 --- a/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs @@ -32,7 +32,6 @@ #endregion using System; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Grpc.Core @@ -40,18 +39,22 @@ namespace Grpc.Core /// <summary> /// Return type for bidirectional streaming calls. /// </summary> + /// <typeparam name="TRequest">Request message type for this call.</typeparam> + /// <typeparam name="TResponse">Response message type for this call.</typeparam> public sealed class AsyncDuplexStreamingCall<TRequest, TResponse> : IDisposable { readonly IClientStreamWriter<TRequest> requestStream; readonly IAsyncStreamReader<TResponse> responseStream; + readonly Task<Metadata> responseHeadersAsync; readonly Func<Status> getStatusFunc; readonly Func<Metadata> getTrailersFunc; readonly Action disposeAction; - public AsyncDuplexStreamingCall(IClientStreamWriter<TRequest> requestStream, IAsyncStreamReader<TResponse> responseStream, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) + internal AsyncDuplexStreamingCall(IClientStreamWriter<TRequest> requestStream, IAsyncStreamReader<TResponse> responseStream, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) { this.requestStream = requestStream; this.responseStream = responseStream; + this.responseHeadersAsync = responseHeadersAsync; this.getStatusFunc = getStatusFunc; this.getTrailersFunc = getTrailersFunc; this.disposeAction = disposeAction; @@ -80,6 +83,17 @@ namespace Grpc.Core } /// <summary> + /// Asynchronous access to response headers. + /// </summary> + public Task<Metadata> ResponseHeadersAsync + { + get + { + return this.responseHeadersAsync; + } + } + + /// <summary> /// Gets the call status if the call has already finished. /// Throws InvalidOperationException otherwise. /// </summary> diff --git a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs index 380efcdb0e..f953091984 100644 --- a/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs +++ b/src/csharp/Grpc.Core/AsyncServerStreamingCall.cs @@ -32,7 +32,6 @@ #endregion using System; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Grpc.Core @@ -40,16 +39,19 @@ namespace Grpc.Core /// <summary> /// Return type for server streaming calls. /// </summary> + /// <typeparam name="TResponse">Response message type for this call.</typeparam> public sealed class AsyncServerStreamingCall<TResponse> : IDisposable { readonly IAsyncStreamReader<TResponse> responseStream; + readonly Task<Metadata> responseHeadersAsync; readonly Func<Status> getStatusFunc; readonly Func<Metadata> getTrailersFunc; readonly Action disposeAction; - public AsyncServerStreamingCall(IAsyncStreamReader<TResponse> responseStream, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) + internal AsyncServerStreamingCall(IAsyncStreamReader<TResponse> responseStream, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) { this.responseStream = responseStream; + this.responseHeadersAsync = responseHeadersAsync; this.getStatusFunc = getStatusFunc; this.getTrailersFunc = getTrailersFunc; this.disposeAction = disposeAction; @@ -67,6 +69,17 @@ namespace Grpc.Core } /// <summary> + /// Asynchronous access to response headers. + /// </summary> + public Task<Metadata> ResponseHeadersAsync + { + get + { + return this.responseHeadersAsync; + } + } + + /// <summary> /// Gets the call status if the call has already finished. /// Throws InvalidOperationException otherwise. /// </summary> diff --git a/src/csharp/Grpc.Core/AsyncUnaryCall.cs b/src/csharp/Grpc.Core/AsyncUnaryCall.cs index 224e343916..97df8f5e91 100644 --- a/src/csharp/Grpc.Core/AsyncUnaryCall.cs +++ b/src/csharp/Grpc.Core/AsyncUnaryCall.cs @@ -40,16 +40,19 @@ namespace Grpc.Core /// <summary> /// Return type for single request - single response call. /// </summary> + /// <typeparam name="TResponse">Response message type for this call.</typeparam> public sealed class AsyncUnaryCall<TResponse> : IDisposable { readonly Task<TResponse> responseAsync; + readonly Task<Metadata> responseHeadersAsync; readonly Func<Status> getStatusFunc; readonly Func<Metadata> getTrailersFunc; readonly Action disposeAction; - public AsyncUnaryCall(Task<TResponse> responseAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) + internal AsyncUnaryCall(Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) { this.responseAsync = responseAsync; + this.responseHeadersAsync = responseHeadersAsync; this.getStatusFunc = getStatusFunc; this.getTrailersFunc = getTrailersFunc; this.disposeAction = disposeAction; @@ -67,6 +70,17 @@ namespace Grpc.Core } /// <summary> + /// Asynchronous access to response headers. + /// </summary> + public Task<Metadata> ResponseHeadersAsync + { + get + { + return this.responseHeadersAsync; + } + } + + /// <summary> /// Allows awaiting this object directly. /// </summary> public TaskAwaiter<TResponse> GetAwaiter() diff --git a/src/csharp/Grpc.Core/CallInvocationDetails.cs b/src/csharp/Grpc.Core/CallInvocationDetails.cs index 6565073fc5..8228b8f317 100644 --- a/src/csharp/Grpc.Core/CallInvocationDetails.cs +++ b/src/csharp/Grpc.Core/CallInvocationDetails.cs @@ -40,6 +40,8 @@ namespace Grpc.Core /// <summary> /// Details about a client-side call to be invoked. /// </summary> + /// <typeparam name="TRequest">Request message type for the call.</typeparam> + /// <typeparam name="TResponse">Response message type for the call.</typeparam> public struct CallInvocationDetails<TRequest, TResponse> { readonly Channel channel; @@ -50,7 +52,7 @@ namespace Grpc.Core CallOptions options; /// <summary> - /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails{TRequest,TResponse}"/> struct. /// </summary> /// <param name="channel">Channel to use for this call.</param> /// <param name="method">Method to call.</param> @@ -61,7 +63,7 @@ namespace Grpc.Core } /// <summary> - /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails{TRequest,TResponse}"/> struct. /// </summary> /// <param name="channel">Channel to use for this call.</param> /// <param name="method">Method to call.</param> @@ -73,7 +75,7 @@ namespace Grpc.Core } /// <summary> - /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails{TRequest,TResponse}"/> struct. /// </summary> /// <param name="channel">Channel to use for this call.</param> /// <param name="method">Qualified method name.</param> @@ -158,7 +160,7 @@ namespace Grpc.Core } /// <summary> - /// Returns new instance of <see cref="CallInvocationDetails"/> with + /// Returns new instance of <see cref="CallInvocationDetails{TRequest, TResponse}"/> with /// <c>Options</c> set to the value provided. Values of all other fields are preserved. /// </summary> public CallInvocationDetails<TRequest, TResponse> WithOptions(CallOptions options) diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs index 3dfe80b48c..c3bc9c3156 100644 --- a/src/csharp/Grpc.Core/CallOptions.cs +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -118,6 +118,7 @@ namespace Grpc.Core /// Returns new instance of <see cref="CallOptions"/> with /// <c>Headers</c> set to the value provided. Values of all other fields are preserved. /// </summary> + /// <param name="headers">The headers.</param> public CallOptions WithHeaders(Metadata headers) { var newOptions = this; @@ -129,6 +130,7 @@ namespace Grpc.Core /// Returns new instance of <see cref="CallOptions"/> with /// <c>Deadline</c> set to the value provided. Values of all other fields are preserved. /// </summary> + /// <param name="deadline">The deadline.</param> public CallOptions WithDeadline(DateTime deadline) { var newOptions = this; @@ -140,6 +142,7 @@ namespace Grpc.Core /// Returns new instance of <see cref="CallOptions"/> with /// <c>CancellationToken</c> set to the value provided. Values of all other fields are preserved. /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> public CallOptions WithCancellationToken(CancellationToken cancellationToken) { var newOptions = this; diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs index 7067456638..94b3c2fe65 100644 --- a/src/csharp/Grpc.Core/Calls.cs +++ b/src/csharp/Grpc.Core/Calls.cs @@ -74,7 +74,7 @@ namespace Grpc.Core { var asyncCall = new AsyncCall<TRequest, TResponse>(call); var asyncResult = asyncCall.UnaryCallAsync(req); - return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); + return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } /// <summary> @@ -93,13 +93,14 @@ namespace Grpc.Core var asyncCall = new AsyncCall<TRequest, TResponse>(call); asyncCall.StartServerStreamingCall(req); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); - return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); + return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } /// <summary> /// Invokes a client streaming call asynchronously. /// In client streaming scenario, client sends a stream of requests and server responds with a single response. /// </summary> + /// <param name="call">The call defintion.</param> /// <returns>An awaitable call object providing access to the response.</returns> /// <typeparam name="TRequest">Type of request messages.</typeparam> /// <typeparam name="TResponse">The of response message.</typeparam> @@ -110,7 +111,7 @@ namespace Grpc.Core var asyncCall = new AsyncCall<TRequest, TResponse>(call); var resultTask = asyncCall.ClientStreamingCallAsync(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); - return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); + return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } /// <summary> @@ -130,7 +131,7 @@ namespace Grpc.Core asyncCall.StartDuplexStreamingCall(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); - return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); + return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index 64c6adf2bf..f1942727cd 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -43,17 +43,23 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// gRPC Channel + /// Represents a gRPC channel. Channels are an abstraction of long-lived connections to remote servers. + /// More client objects can reuse the same channel. Creating a channel is an expensive operation compared to invoking + /// a remote call so in general you should reuse a single channel for as many calls as possible. /// </summary> - public class Channel : IDisposable + public class Channel { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>(); + readonly object myLock = new object(); + readonly AtomicCounter activeCallCounter = new AtomicCounter(); + readonly string target; readonly GrpcEnvironment environment; readonly ChannelSafeHandle handle; readonly List<ChannelOption> options; - bool disposed; + + bool shutdownRequested; /// <summary> /// Creates a channel that connects to a specific host. @@ -65,7 +71,7 @@ namespace Grpc.Core public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null) { this.target = Preconditions.CheckNotNull(target, "target"); - this.environment = GrpcEnvironment.GetInstance(); + this.environment = GrpcEnvironment.AddRef(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); EnsureUserAgentChannelOption(this.options); @@ -157,6 +163,7 @@ namespace Grpc.Core /// 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> + /// <param name="deadline">The deadline. <c>null</c> indicates no deadline.</param> public async Task ConnectAsync(DateTime? deadline = null) { var currentState = handle.CheckConnectivityState(true); @@ -172,12 +179,26 @@ namespace Grpc.Core } /// <summary> - /// Destroys the underlying channel. + /// Waits until there are no more active calls for this channel and then cleans up + /// resources used by this channel. /// </summary> - public void Dispose() + public async Task ShutdownAsync() { - Dispose(true); - GC.SuppressFinalize(this); + lock (myLock) + { + Preconditions.CheckState(!shutdownRequested); + shutdownRequested = true; + } + + var activeCallCount = activeCallCounter.Count; + if (activeCallCount > 0) + { + Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount); + } + + handle.Dispose(); + + await Task.Run(() => GrpcEnvironment.Release()); } internal ChannelSafeHandle Handle @@ -196,13 +217,20 @@ namespace Grpc.Core } } - protected virtual void Dispose(bool disposing) + internal void AddCallReference(object call) { - if (disposing && handle != null && !disposed) - { - disposed = true; - handle.Dispose(); - } + activeCallCounter.Increment(); + + bool success = false; + handle.DangerousAddRef(ref success); + Preconditions.CheckState(success); + } + + internal void RemoveCallReference(object call) + { + handle.DangerousRelease(); + + activeCallCounter.Decrement(); } private static void EnsureUserAgentChannelOption(List<ChannelOption> options) diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs index 0cb2953f2c..f5ef63af54 100644 --- a/src/csharp/Grpc.Core/ChannelOptions.cs +++ b/src/csharp/Grpc.Core/ChannelOptions.cs @@ -44,9 +44,19 @@ namespace Grpc.Core /// </summary> public sealed class ChannelOption { + /// <summary> + /// Type of <c>ChannelOption</c>. + /// </summary> public enum OptionType { + /// <summary> + /// Channel option with integer value. + /// </summary> Integer, + + /// <summary> + /// Channel option with string value. + /// </summary> String } @@ -71,7 +81,7 @@ namespace Grpc.Core /// Creates a channel option with an integer value. /// </summary> /// <param name="name">Name.</param> - /// <param name="stringValue">String value.</param> + /// <param name="intValue">Integer value.</param> public ChannelOption(string name, int intValue) { this.type = OptionType.Integer; @@ -79,6 +89,9 @@ namespace Grpc.Core this.intValue = intValue; } + /// <summary> + /// Gets the type of the <c>ChannelOption</c>. + /// </summary> public OptionType Type { get @@ -87,6 +100,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the name of the <c>ChannelOption</c>. + /// </summary> public string Name { get @@ -95,6 +111,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the integer value the <c>ChannelOption</c>. + /// </summary> public int IntValue { get @@ -104,6 +123,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the string value the <c>ChannelOption</c>. + /// </summary> public string StringValue { get @@ -140,7 +162,7 @@ namespace Grpc.Core /// <summary>Primary user agent: goes at the start of the user-agent metadata</summary> public const string PrimaryUserAgentString = "grpc.primary_user_agent"; - /// <summary> Secondary user agent: goes at the end of the user-agent metadata</summary> + /// <summary>Secondary user agent: goes at the end of the user-agent metadata</summary> public const string SecondaryUserAgentString = "grpc.secondary_user_agent"; /// <summary> diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index f240d777b9..f4533e735c 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -32,15 +32,15 @@ #endregion using System; -using System.Collections.Generic; using System.Text.RegularExpressions; - -using Grpc.Core.Internal; -using Grpc.Core.Utils; +using System.Threading.Tasks; namespace Grpc.Core { - public delegate void MetadataInterceptorDelegate(string authUri, Metadata metadata); + /// <summary> + /// Interceptor for call headers. + /// </summary> + public delegate void HeaderInterceptor(IMethod method, string authUri, Metadata metadata); /// <summary> /// Base class for client-side stubs. @@ -53,6 +53,10 @@ namespace Grpc.Core readonly Channel channel; readonly string authUriBase; + /// <summary> + /// Initializes a new instance of <c>ClientBase</c> class. + /// </summary> + /// <param name="channel">The channel to use for remote call invocation.</param> public ClientBase(Channel channel) { this.channel = channel; @@ -60,10 +64,10 @@ namespace Grpc.Core } /// <summary> - /// Can be used to register a custom header (initial metadata) interceptor. - /// The delegate each time before a new call on this client is started. + /// Can be used to register a custom header (request metadata) interceptor. + /// The interceptor is invoked each time a new call on this client is started. /// </summary> - public MetadataInterceptorDelegate HeaderInterceptor + public HeaderInterceptor HeaderInterceptor { get; set; @@ -95,6 +99,11 @@ namespace Grpc.Core /// <summary> /// Creates a new call to given method. /// </summary> + /// <param name="method">The method to invoke.</param> + /// <param name="options">The call options.</param> + /// <typeparam name="TRequest">Request message type.</typeparam> + /// <typeparam name="TResponse">Response message type.</typeparam> + /// <returns>The call invocation details.</returns> protected CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, CallOptions options) where TRequest : class where TResponse : class @@ -107,7 +116,7 @@ namespace Grpc.Core options = options.WithHeaders(new Metadata()); } var authUri = authUriBase != null ? authUriBase + method.ServiceName : null; - interceptor(authUri, options.Headers); + interceptor(method, authUri, options.Headers); } return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options); } @@ -119,7 +128,8 @@ namespace Grpc.Core internal static string GetAuthUriBase(string target) { var match = ChannelTargetPattern.Match(target); - if (!match.Success) { + if (!match.Success) + { return null; } return "https://" + match.Groups[2].Value + "/"; diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs index 2e4bfc9e47..1d899b97fd 100644 --- a/src/csharp/Grpc.Core/ContextPropagationToken.cs +++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs @@ -44,8 +44,8 @@ namespace Grpc.Core /// In situations when a backend is making calls to another backend, /// it makes sense to propagate properties like deadline and cancellation /// token of the server call to the child call. - /// C core provides some other contexts (like tracing context) that - /// are not accessible to C# layer, but this token still allows propagating them. + /// The gRPC native layer provides some other contexts (like tracing context) that + /// are not accessible to explicitly C# layer, but this token still allows propagating them. /// </summary> public class ContextPropagationToken { @@ -132,7 +132,6 @@ namespace Grpc.Core bool propagateDeadline; bool propagateCancellation; - /// <summary> /// Creates new context propagation options. /// </summary> @@ -144,13 +143,13 @@ namespace Grpc.Core this.propagateCancellation = propagateCancellation; } - /// <value><c>true</c> if parent call's deadline should be propagated to the child call.</value> + /// <summary><c>true</c> if parent call's deadline should be propagated to the child call.</summary> public bool IsPropagateDeadline { get { return this.propagateDeadline; } } - /// <value><c>true</c> if parent call's cancellation token should be propagated to the child call.</value> + /// <summary><c>true</c> if parent call's cancellation token should be propagated to the child call.</summary> public bool IsPropagateCancellation { get { return this.propagateCancellation; } diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index 055aff1444..ad2af17bc7 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -49,6 +49,7 @@ <Compile Include="AsyncDuplexStreamingCall.cs" /> <Compile Include="AsyncServerStreamingCall.cs" /> <Compile Include="IClientStreamWriter.cs" /> + <Compile Include="Internal\INativeCall.cs" /> <Compile Include="IServerStreamWriter.cs" /> <Compile Include="IAsyncStreamWriter.cs" /> <Compile Include="IAsyncStreamReader.cs" /> diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs index 30d8c80235..e7c04185c2 100644 --- a/src/csharp/Grpc.Core/GrpcEnvironment.cs +++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs @@ -58,6 +58,7 @@ namespace Grpc.Core static object staticLock = new object(); static GrpcEnvironment instance; + static int refCount; static ILogger logger = new ConsoleLogger(); @@ -67,13 +68,14 @@ namespace Grpc.Core bool isClosed; /// <summary> - /// Returns an instance of initialized gRPC environment. - /// Subsequent invocations return the same instance unless Shutdown has been called first. + /// Returns a reference-counted instance of initialized gRPC environment. + /// Subsequent invocations return the same instance unless reference count has dropped to zero previously. /// </summary> - internal static GrpcEnvironment GetInstance() + internal static GrpcEnvironment AddRef() { lock (staticLock) { + refCount++; if (instance == null) { instance = new GrpcEnvironment(); @@ -83,14 +85,16 @@ namespace Grpc.Core } /// <summary> - /// Shuts down the gRPC environment if it was initialized before. - /// Blocks until the environment has been fully shutdown. + /// 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). /// </summary> - public static void Shutdown() + internal static void Release() { lock (staticLock) { - if (instance != null) + Preconditions.CheckState(refCount > 0); + refCount--; + if (refCount == 0) { instance.Close(); instance = null; @@ -98,6 +102,14 @@ namespace Grpc.Core } } + internal static int GetRefCount() + { + lock (staticLock) + { + return refCount; + } + } + /// <summary> /// Gets application-wide logger used by gRPC. /// </summary> @@ -125,12 +137,10 @@ namespace Grpc.Core private GrpcEnvironment() { NativeLogRedirector.Redirect(); - grpcsharp_init(); + GrpcNativeInit(); completionRegistry = new CompletionRegistry(this); threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE); threadPool.Start(); - // TODO: use proper logging here - Logger.Info("gRPC initialized."); } /// <summary> @@ -175,6 +185,16 @@ namespace Grpc.Core return Marshal.PtrToStringAnsi(ptr); } + internal static void GrpcNativeInit() + { + grpcsharp_init(); + } + + internal static void GrpcNativeShutdown() + { + grpcsharp_shutdown(); + } + /// <summary> /// Shuts down this environment. /// </summary> @@ -185,12 +205,10 @@ namespace Grpc.Core throw new InvalidOperationException("Close has already been called"); } threadPool.Stop(); - grpcsharp_shutdown(); + GrpcNativeShutdown(); isClosed = true; debugStats.CheckOK(); - - Logger.Info("gRPC shutdown."); } } } diff --git a/src/csharp/Grpc.Core/IAsyncStreamReader.cs b/src/csharp/Grpc.Core/IAsyncStreamReader.cs index c0a0674e50..49e1ea7832 100644 --- a/src/csharp/Grpc.Core/IAsyncStreamReader.cs +++ b/src/csharp/Grpc.Core/IAsyncStreamReader.cs @@ -42,7 +42,7 @@ namespace Grpc.Core /// <summary> /// A stream of messages to be read. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The message type.</typeparam> public interface IAsyncStreamReader<T> : IAsyncEnumerator<T> { // TODO(jtattermusch): consider just using IAsyncEnumerator instead of this interface. diff --git a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs index 4e2acb9c71..9c0d2d312e 100644 --- a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs +++ b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs @@ -42,7 +42,7 @@ namespace Grpc.Core /// <summary> /// A writable stream of messages. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The message type.</typeparam> public interface IAsyncStreamWriter<T> { /// <summary> @@ -56,7 +56,7 @@ namespace Grpc.Core /// If null, default options will be used. /// Once set, this property maintains its value across subsequent /// writes. - /// <value>The write options.</value> + /// </summary> WriteOptions WriteOptions { get; set; } } } diff --git a/src/csharp/Grpc.Core/IClientStreamWriter.cs b/src/csharp/Grpc.Core/IClientStreamWriter.cs index a3028bc374..3fd0774db5 100644 --- a/src/csharp/Grpc.Core/IClientStreamWriter.cs +++ b/src/csharp/Grpc.Core/IClientStreamWriter.cs @@ -42,7 +42,7 @@ namespace Grpc.Core /// <summary> /// Client-side writable stream of messages with Close capability. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The message type.</typeparam> public interface IClientStreamWriter<T> : IAsyncStreamWriter<T> { /// <summary> diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index 2c3e3d75ea..be5d611a53 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -51,22 +51,35 @@ namespace Grpc.Core.Internal static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>(); readonly CallInvocationDetails<TRequest, TResponse> details; + readonly INativeCall injectedNativeCall; // for testing // Completion of a pending unary response if not null. TaskCompletionSource<TResponse> unaryResponseTcs; + // Indicates that steaming call has finished. + TaskCompletionSource<object> streamingCallFinishedTcs = new TaskCompletionSource<object>(); + + // Response headers set here once received. + TaskCompletionSource<Metadata> responseHeadersTcs = new TaskCompletionSource<Metadata>(); + // Set after status is received. Used for both unary and streaming response calls. ClientSideStatus? finishedStatus; - bool readObserverCompleted; // True if readObserver has already been completed. - public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails) - : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer) + : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer, callDetails.Channel.Environment) { this.details = callDetails.WithOptions(callDetails.Options.Normalize()); this.initialMetadataSent = true; // we always send metadata at the very beginning of the call. } + /// <summary> + /// This constructor should only be used for testing. + /// </summary> + public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails, INativeCall injectedNativeCall) : this(callDetails) + { + this.injectedNativeCall = injectedNativeCall; + } + // TODO: this method is not Async, so it shouldn't be in AsyncCall class, but // it is reusing fair amount of code in this class, so we are leaving it here. /// <summary> @@ -100,7 +113,7 @@ namespace Grpc.Core.Internal bool success = (ev.success != 0); try { - HandleUnaryResponse(success, ctx); + HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata()); } catch (Exception e) { @@ -125,7 +138,7 @@ namespace Grpc.Core.Internal Preconditions.CheckState(!started); started = true; - Initialize(details.Channel.Environment.CompletionQueue); + Initialize(environment.CompletionQueue); halfcloseRequested = true; readingDone = true; @@ -152,7 +165,7 @@ namespace Grpc.Core.Internal Preconditions.CheckState(!started); started = true; - Initialize(details.Channel.Environment.CompletionQueue); + Initialize(environment.CompletionQueue); readingDone = true; @@ -176,10 +189,9 @@ namespace Grpc.Core.Internal Preconditions.CheckState(!started); started = true; - Initialize(details.Channel.Environment.CompletionQueue); + Initialize(environment.CompletionQueue); halfcloseRequested = true; - halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called. byte[] payload = UnsafeSerialize(msg); @@ -187,6 +199,7 @@ namespace Grpc.Core.Internal { call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall()); } + call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders); } } @@ -201,12 +214,13 @@ namespace Grpc.Core.Internal Preconditions.CheckState(!started); started = true; - Initialize(details.Channel.Environment.CompletionQueue); + Initialize(environment.CompletionQueue); using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { call.StartDuplexStreaming(HandleFinished, metadataArray); } + call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders); } } @@ -248,6 +262,28 @@ namespace Grpc.Core.Internal } /// <summary> + /// Get the task that completes once if streaming call finishes with ok status and throws RpcException with given status otherwise. + /// </summary> + public Task StreamingCallFinishedTask + { + get + { + return streamingCallFinishedTcs.Task; + } + } + + /// <summary> + /// Get the task that completes once response headers are received. + /// </summary> + public Task<Metadata> ResponseHeadersAsync + { + get + { + return responseHeadersTcs.Task; + } + } + + /// <summary> /// Gets the resulting status if the call has already finished. /// Throws InvalidOperationException otherwise. /// </summary> @@ -281,51 +317,31 @@ namespace Grpc.Core.Internal } } - /// <summary> - /// On client-side, we only fire readCompletionDelegate once all messages have been read - /// and status has been received. - /// </summary> - protected override void ProcessLastRead(AsyncCompletionDelegate<TResponse> completionDelegate) + protected override void OnAfterReleaseResources() { - if (completionDelegate != null && readingDone && finishedStatus.HasValue) - { - bool shouldComplete; - lock (myLock) - { - shouldComplete = !readObserverCompleted; - readObserverCompleted = true; - } - - if (shouldComplete) - { - var status = finishedStatus.Value.Status; - if (status.StatusCode != StatusCode.OK) - { - FireCompletion(completionDelegate, default(TResponse), new RpcException(status)); - } - else - { - FireCompletion(completionDelegate, default(TResponse), null); - } - } - } + details.Channel.RemoveCallReference(this); } - protected override void OnReleaseResources() + private void Initialize(CompletionQueueSafeHandle cq) { - details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement(); + var call = CreateNativeCall(cq); + details.Channel.AddCallReference(this); + InitializeInternal(call); + RegisterCancellationCallback(); } - private void Initialize(CompletionQueueSafeHandle cq) + private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq) { + if (injectedNativeCall != null) + { + return injectedNativeCall; // allows injecting a mock INativeCall in tests. + } + var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; - var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry, + return details.Channel.Handle.CreateCall(environment.CompletionRegistry, parentCall, ContextPropagationToken.DefaultMask, cq, details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); - details.Channel.Environment.DebugStats.ActiveClientCalls.Increment(); - InitializeInternal(call); - RegisterCancellationCallback(); } // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called. @@ -348,31 +364,31 @@ namespace Grpc.Core.Internal } /// <summary> - /// Handler for unary response completion. + /// Handles receive status completion for calls with streaming response. /// </summary> - private void HandleUnaryResponse(bool success, BatchContextSafeHandle ctx) + private void HandleReceivedResponseHeaders(bool success, Metadata responseHeaders) { - var fullStatus = ctx.GetReceivedStatusOnClient(); + responseHeadersTcs.SetResult(responseHeaders); + } + /// <summary> + /// Handler for unary response completion. + /// </summary> + private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders) + { lock (myLock) { finished = true; - finishedStatus = fullStatus; - - halfclosed = true; + finishedStatus = receivedStatus; ReleaseResourcesIfPossible(); } - if (!success) - { - unaryResponseTcs.SetException(new RpcException(new Status(StatusCode.Internal, "Internal error occured."))); - return; - } + responseHeadersTcs.SetResult(responseHeaders); - var status = fullStatus.Status; + var status = receivedStatus.Status; - if (status.StatusCode != StatusCode.OK) + if (!success || status.StatusCode != StatusCode.OK) { unaryResponseTcs.SetException(new RpcException(status)); return; @@ -380,7 +396,7 @@ namespace Grpc.Core.Internal // TODO: handle deserialization error TResponse msg; - TryDeserialize(ctx.GetReceivedMessage(), out msg); + TryDeserialize(receivedMessage, out msg); unaryResponseTcs.SetResult(msg); } @@ -388,22 +404,25 @@ namespace Grpc.Core.Internal /// <summary> /// Handles receive status completion for calls with streaming response. /// </summary> - private void HandleFinished(bool success, BatchContextSafeHandle ctx) + private void HandleFinished(bool success, ClientSideStatus receivedStatus) { - var fullStatus = ctx.GetReceivedStatusOnClient(); - - AsyncCompletionDelegate<TResponse> origReadCompletionDelegate = null; lock (myLock) { finished = true; - finishedStatus = fullStatus; - - origReadCompletionDelegate = readCompletionDelegate; + finishedStatus = receivedStatus; ReleaseResourcesIfPossible(); } - ProcessLastRead(origReadCompletionDelegate); + var status = receivedStatus.Status; + + if (!success || status.StatusCode != StatusCode.OK) + { + streamingCallFinishedTcs.SetException(new RpcException(status)); + return; + } + + streamingCallFinishedTcs.SetResult(null); } } }
\ No newline at end of file diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs index 6ca4bbdafc..4d20394644 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs @@ -54,30 +54,30 @@ namespace Grpc.Core.Internal readonly Func<TWrite, byte[]> serializer; readonly Func<byte[], TRead> deserializer; + protected readonly GrpcEnvironment environment; protected readonly object myLock = new object(); - protected CallSafeHandle call; + protected INativeCall call; protected bool disposed; protected bool started; - protected bool errorOccured; protected bool cancelRequested; protected AsyncCompletionDelegate<object> sendCompletionDelegate; // Completion of a pending send or sendclose if not null. protected AsyncCompletionDelegate<TRead> readCompletionDelegate; // Completion of a pending send or sendclose if not null. - protected bool readingDone; - protected bool halfcloseRequested; - protected bool halfclosed; + protected bool readingDone; // True if last read (i.e. read with null payload) was already received. + protected bool halfcloseRequested; // True if send close have been initiated. protected bool finished; // True if close has been received from the peer. protected bool initialMetadataSent; - protected long streamingWritesCounter; + protected long streamingWritesCounter; // Number of streaming send operations started so far. - public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer) + public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer, GrpcEnvironment environment) { this.serializer = Preconditions.CheckNotNull(serializer); this.deserializer = Preconditions.CheckNotNull(deserializer); + this.environment = Preconditions.CheckNotNull(environment); } /// <summary> @@ -114,7 +114,7 @@ namespace Grpc.Core.Internal } } - protected void InitializeInternal(CallSafeHandle call) + protected void InitializeInternal(INativeCall call) { lock (myLock) { @@ -159,16 +159,6 @@ namespace Grpc.Core.Internal } } - // TODO(jtattermusch): find more fitting name for this method. - /// <summary> - /// Default behavior just completes the read observer, but more sofisticated behavior might be required - /// by subclasses. - /// </summary> - protected virtual void ProcessLastRead(AsyncCompletionDelegate<TRead> completionDelegate) - { - FireCompletion(completionDelegate, default(TRead), null); - } - /// <summary> /// If there are no more pending actions and no new actions can be started, releases /// the underlying native resources. @@ -177,7 +167,7 @@ namespace Grpc.Core.Internal { if (!disposed && call != null) { - bool noMoreSendCompletions = halfclosed || (cancelRequested && sendCompletionDelegate == null); + bool noMoreSendCompletions = sendCompletionDelegate == null && (halfcloseRequested || cancelRequested || finished); if (noMoreSendCompletions && readingDone && finished) { ReleaseResources(); @@ -189,34 +179,33 @@ namespace Grpc.Core.Internal private void ReleaseResources() { - OnReleaseResources(); if (call != null) { call.Dispose(); } disposed = true; + OnAfterReleaseResources(); } - protected virtual void OnReleaseResources() + protected virtual void OnAfterReleaseResources() { } protected void CheckSendingAllowed() { Preconditions.CheckState(started); - Preconditions.CheckState(!errorOccured); CheckNotCancelled(); Preconditions.CheckState(!disposed); Preconditions.CheckState(!halfcloseRequested, "Already halfclosed."); + Preconditions.CheckState(!finished, "Already finished."); Preconditions.CheckState(sendCompletionDelegate == null, "Only one write can be pending at a time"); } - protected void CheckReadingAllowed() + protected virtual void CheckReadingAllowed() { Preconditions.CheckState(started); Preconditions.CheckState(!disposed); - Preconditions.CheckState(!errorOccured); Preconditions.CheckState(!readingDone, "Stream has already been closed."); Preconditions.CheckState(readCompletionDelegate == null, "Only one read can be pending at a time"); @@ -280,7 +269,7 @@ namespace Grpc.Core.Internal /// <summary> /// Handles send completion. /// </summary> - protected void HandleSendFinished(bool success, BatchContextSafeHandle ctx) + protected void HandleSendFinished(bool success) { AsyncCompletionDelegate<object> origCompletionDelegate = null; lock (myLock) @@ -304,12 +293,11 @@ namespace Grpc.Core.Internal /// <summary> /// Handles halfclose completion. /// </summary> - protected void HandleHalfclosed(bool success, BatchContextSafeHandle ctx) + protected void HandleHalfclosed(bool success) { AsyncCompletionDelegate<object> origCompletionDelegate = null; lock (myLock) { - halfclosed = true; origCompletionDelegate = sendCompletionDelegate; sendCompletionDelegate = null; @@ -329,23 +317,17 @@ namespace Grpc.Core.Internal /// <summary> /// Handles streaming read completion. /// </summary> - protected void HandleReadFinished(bool success, BatchContextSafeHandle ctx) + protected void HandleReadFinished(bool success, byte[] receivedMessage) { - var payload = ctx.GetReceivedMessage(); - AsyncCompletionDelegate<TRead> origCompletionDelegate = null; lock (myLock) { origCompletionDelegate = readCompletionDelegate; - if (payload != null) - { - readCompletionDelegate = null; - } - else + readCompletionDelegate = null; + + if (receivedMessage == null) { - // This was the last read. Keeping the readCompletionDelegate - // to be either fired by this handler or by client-side finished - // handler. + // This was the last read. readingDone = true; } @@ -354,17 +336,17 @@ namespace Grpc.Core.Internal // TODO: handle the case when error occured... - if (payload != null) + if (receivedMessage != null) { // TODO: handle deserialization error TRead msg; - TryDeserialize(payload, out msg); + TryDeserialize(receivedMessage, out msg); FireCompletion(origCompletionDelegate, msg, null); } else { - ProcessLastRead(origCompletionDelegate); + FireCompletion(origCompletionDelegate, default(TRead), null); } } } diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs index 3710a65d6b..5c47251030 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs @@ -49,17 +49,18 @@ namespace Grpc.Core.Internal { readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>(); readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - readonly GrpcEnvironment environment; + readonly Server server; - public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment) : base(serializer, deserializer) + public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment, Server server) : base(serializer, deserializer, environment) { - this.environment = Preconditions.CheckNotNull(environment); + this.server = Preconditions.CheckNotNull(server); } public void Initialize(CallSafeHandle call) { call.SetCompletionRegistry(environment.CompletionRegistry); - environment.DebugStats.ActiveServerCalls.Increment(); + + server.AddCallReference(this); InitializeInternal(call); } @@ -168,18 +169,22 @@ namespace Grpc.Core.Internal } } - protected override void OnReleaseResources() + protected override void CheckReadingAllowed() { - environment.DebugStats.ActiveServerCalls.Decrement(); + base.CheckReadingAllowed(); + Preconditions.CheckArgument(!cancelRequested); + } + + protected override void OnAfterReleaseResources() + { + server.RemoveCallReference(this); } /// <summary> /// Handles the server side close completion. /// </summary> - private void HandleFinishedServerside(bool success, BatchContextSafeHandle ctx) + private void HandleFinishedServerside(bool success, bool cancelled) { - bool cancelled = ctx.GetReceivedCloseOnServerCancelled(); - lock (myLock) { finished = true; diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs index 6a2add54db..3a96414bea 100644 --- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs @@ -134,7 +134,7 @@ namespace Grpc.Core.Internal } // Gets data of server_rpc_new completion. - public ServerRpcNew GetServerRpcNew() + public ServerRpcNew GetServerRpcNew(Server server) { var call = grpcsharp_batch_context_server_rpc_new_call(this); @@ -145,7 +145,7 @@ namespace Grpc.Core.Internal IntPtr metadataArrayPtr = grpcsharp_batch_context_server_rpc_new_request_metadata(this); var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr); - return new ServerRpcNew(call, method, host, deadline, metadata); + return new ServerRpcNew(server, call, method, host, deadline, metadata); } // Gets data of receive_close_on_server completion. @@ -198,14 +198,16 @@ namespace Grpc.Core.Internal /// </summary> internal struct ServerRpcNew { + readonly Server server; readonly CallSafeHandle call; readonly string method; readonly string host; readonly Timespec deadline; readonly Metadata requestMetadata; - public ServerRpcNew(CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata) + public ServerRpcNew(Server server, CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata) { + this.server = server; this.call = call; this.method = method; this.host = host; @@ -213,6 +215,14 @@ namespace Grpc.Core.Internal this.requestMetadata = requestMetadata; } + public Server Server + { + get + { + return this.server; + } + } + public CallSafeHandle Call { get diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs index 3cb01e29bd..c3611a7761 100644 --- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs @@ -38,9 +38,9 @@ using Grpc.Core.Utils; namespace Grpc.Core.Internal { /// <summary> - /// grpc_call from <grpc/grpc.h> + /// grpc_call from <c>grpc/grpc.h</c> /// </summary> - internal class CallSafeHandle : SafeHandleZeroIsInvalid + internal class CallSafeHandle : SafeHandleZeroIsInvalid, INativeCall { public static readonly CallSafeHandle NullInstance = new CallSafeHandle(); @@ -87,6 +87,10 @@ namespace Grpc.Core.Internal BatchContextSafeHandle ctx); [DllImport("grpc_csharp_ext.dll")] + static extern GRPCCallError grpcsharp_call_recv_initial_metadata(CallSafeHandle call, + BatchContextSafeHandle ctx); + + [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_start_serverside(CallSafeHandle call, BatchContextSafeHandle ctx); @@ -109,10 +113,10 @@ namespace Grpc.Core.Internal this.completionRegistry = completionRegistry; } - public void StartUnary(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) + public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata())); grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags) .CheckOk(); } @@ -123,66 +127,73 @@ namespace Grpc.Core.Internal .CheckOk(); } - public void StartClientStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata())); grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk(); } - public void StartServerStreaming(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) + public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient())); grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk(); } - public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient())); grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk(); } - public void StartSendMessage(BatchCompletionDelegate callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata) + public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success)); grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk(); } - public void StartSendCloseFromClient(BatchCompletionDelegate callback) + public void StartSendCloseFromClient(SendCompletionHandler callback) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success)); grpcsharp_call_send_close_from_client(this, ctx).CheckOk(); } - public void StartSendStatusFromServer(BatchCompletionDelegate callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) + public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success)); grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk(); } - public void StartReceiveMessage(BatchCompletionDelegate callback) + public void StartReceiveMessage(ReceivedMessageHandler callback) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedMessage())); grpcsharp_call_recv_message(this, ctx).CheckOk(); } - public void StartServerSide(BatchCompletionDelegate callback) + public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback) + { + var ctx = BatchContextSafeHandle.Create(); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedInitialMetadata())); + grpcsharp_call_recv_initial_metadata(this, ctx).CheckOk(); + } + + public void StartServerSide(ReceivedCloseOnServerHandler callback) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedCloseOnServerCancelled())); grpcsharp_call_start_serverside(this, ctx).CheckOk(); } - public void StartSendInitialMetadata(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - completionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success)); grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk(); } diff --git a/src/csharp/Grpc.Core/Internal/ChannelArgsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelArgsSafeHandle.cs index c12aec5a3a..ea5b52374e 100644 --- a/src/csharp/Grpc.Core/Internal/ChannelArgsSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ChannelArgsSafeHandle.cs @@ -35,7 +35,7 @@ using System.Threading.Tasks; namespace Grpc.Core.Internal { /// <summary> - /// grpc_channel_args from <grpc/grpc.h> + /// grpc_channel_args from <c>grpc/grpc.h</c> /// </summary> internal class ChannelArgsSafeHandle : SafeHandleZeroIsInvalid { diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs index 7f03bf4ea5..7a1c6e3dac 100644 --- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs @@ -36,7 +36,7 @@ using System.Threading.Tasks; namespace Grpc.Core.Internal { /// <summary> - /// grpc_channel from <grpc/grpc.h> + /// grpc_channel from <c>grpc/grpc.h</c> /// </summary> internal class ChannelSafeHandle : SafeHandleZeroIsInvalid { @@ -68,11 +68,17 @@ namespace Grpc.Core.Internal public static ChannelSafeHandle CreateInsecure(string target, ChannelArgsSafeHandle channelArgs) { + // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle. + // Doing so would make object finalizer crash if we end up abandoning the handle. + GrpcEnvironment.GrpcNativeInit(); return grpcsharp_insecure_channel_create(target, channelArgs); } public static ChannelSafeHandle CreateSecure(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs) { + // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle. + // Doing so would make object finalizer crash if we end up abandoning the handle. + GrpcEnvironment.GrpcNativeInit(); return grpcsharp_secure_channel_create(credentials, target, channelArgs); } @@ -107,6 +113,7 @@ namespace Grpc.Core.Internal protected override bool ReleaseHandle() { grpcsharp_channel_destroy(handle); + GrpcEnvironment.GrpcNativeShutdown(); return true; } } diff --git a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs index 6c44521038..b4a7335c7c 100644 --- a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs +++ b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs @@ -72,7 +72,13 @@ namespace Grpc.Core.Internal call.StartReadMessage(taskSource.CompletionDelegate); var result = await taskSource.Task; this.current = result; - return result != null; + + if (result == null) + { + await call.StreamingCallFinishedTask; + return false; + } + return true; } public void Dispose() diff --git a/src/csharp/Grpc.Core/Internal/CompletionQueueSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CompletionQueueSafeHandle.cs index f64f3d4175..f7a3471bb4 100644 --- a/src/csharp/Grpc.Core/Internal/CompletionQueueSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CompletionQueueSafeHandle.cs @@ -35,7 +35,7 @@ using System.Threading.Tasks; namespace Grpc.Core.Internal { /// <summary> - /// grpc_completion_queue from <grpc/grpc.h> + /// grpc_completion_queue from <c>grpc/grpc.h</c> /// </summary> internal class CompletionQueueSafeHandle : SafeHandleZeroIsInvalid { diff --git a/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs index 8b4fa85e5d..feed335362 100644 --- a/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CredentialsSafeHandle.cs @@ -36,7 +36,7 @@ using System.Threading.Tasks; namespace Grpc.Core.Internal { /// <summary> - /// grpc_credentials from <grpc/grpc_security.h> + /// grpc_credentials from <c>grpc/grpc_security.h</c> /// </summary> internal class CredentialsSafeHandle : SafeHandleZeroIsInvalid { diff --git a/src/csharp/Grpc.Core/Internal/DebugStats.cs b/src/csharp/Grpc.Core/Internal/DebugStats.cs index 8793450ff3..1bea1adf9e 100644 --- a/src/csharp/Grpc.Core/Internal/DebugStats.cs +++ b/src/csharp/Grpc.Core/Internal/DebugStats.cs @@ -38,10 +38,6 @@ namespace Grpc.Core.Internal { internal class DebugStats { - public readonly AtomicCounter ActiveClientCalls = new AtomicCounter(); - - public readonly AtomicCounter ActiveServerCalls = new AtomicCounter(); - public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter(); /// <summary> @@ -49,16 +45,6 @@ namespace Grpc.Core.Internal /// </summary> public void CheckOK() { - var remainingClientCalls = ActiveClientCalls.Count; - if (remainingClientCalls != 0) - { - DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls)); - } - var remainingServerCalls = ActiveServerCalls.Count; - if (remainingServerCalls != 0) - { - DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls)); - } var pendingBatchCompletions = PendingBatchCompletions.Count; if (pendingBatchCompletions != 0) { diff --git a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs index cb4c7c821e..4b7124ee74 100644 --- a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs +++ b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs @@ -83,8 +83,6 @@ namespace Grpc.Core.Internal lock (myLock) { cq.Shutdown(); - - Logger.Info("Waiting for GRPC threads to finish."); foreach (var thread in threads) { thread.Join(); @@ -136,7 +134,6 @@ namespace Grpc.Core.Internal } } while (ev.type != GRPCCompletionType.Shutdown); - Logger.Info("Completion queue has shutdown successfully, thread {0} exiting.", Thread.CurrentThread.Name); } } } diff --git a/src/csharp/Grpc.Core/Internal/INativeCall.cs b/src/csharp/Grpc.Core/Internal/INativeCall.cs new file mode 100644 index 0000000000..cbef599139 --- /dev/null +++ b/src/csharp/Grpc.Core/Internal/INativeCall.cs @@ -0,0 +1,85 @@ +#region Copyright notice and license +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; + +namespace Grpc.Core.Internal +{ + internal delegate void UnaryResponseClientHandler(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders); + + // Received status for streaming response calls. + internal delegate void ReceivedStatusOnClientHandler(bool success, ClientSideStatus receivedStatus); + + internal delegate void ReceivedMessageHandler(bool success, byte[] receivedMessage); + + internal delegate void ReceivedResponseHeadersHandler(bool success, Metadata responseHeaders); + + internal delegate void SendCompletionHandler(bool success); + + internal delegate void ReceivedCloseOnServerHandler(bool success, bool cancelled); + + /// <summary> + /// Abstraction of a native call object. + /// </summary> + internal interface INativeCall : IDisposable + { + void Cancel(); + + void CancelWithStatus(Grpc.Core.Status status); + + string GetPeer(); + + void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags); + + void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags); + + void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray); + + void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags); + + void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray); + + void StartReceiveMessage(ReceivedMessageHandler callback); + + void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback); + + void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray); + + void StartSendMessage(SendCompletionHandler callback, byte[] payload, Grpc.Core.WriteFlags writeFlags, bool sendEmptyInitialMetadata); + + void StartSendCloseFromClient(SendCompletionHandler callback); + + void StartSendStatusFromServer(SendCompletionHandler callback, Grpc.Core.Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata); + + void StartServerSide(ReceivedCloseOnServerHandler callback); + } +} diff --git a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs index 427c16fac6..31b834c979 100644 --- a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs @@ -35,7 +35,7 @@ using System.Threading.Tasks; namespace Grpc.Core.Internal { /// <summary> - /// grpc_metadata_array from <grpc/grpc.h> + /// grpc_metadata_array from <c>grpc/grpc.h</c> /// </summary> internal class MetadataArraySafeHandle : SafeHandleZeroIsInvalid { @@ -70,7 +70,8 @@ namespace Grpc.Core.Internal var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count)); for (int i = 0; i < metadata.Count; i++) { - grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, metadata[i].ValueBytes, new UIntPtr((ulong)metadata[i].ValueBytes.Length)); + var valueBytes = metadata[i].GetSerializedValueUnsafe(); + grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, valueBytes, new UIntPtr((ulong)valueBytes.Length)); } return metadataArray; } @@ -94,7 +95,7 @@ namespace Grpc.Core.Internal string key = Marshal.PtrToStringAnsi(grpcsharp_metadata_array_get_key(metadataArray, index)); var bytes = new byte[grpcsharp_metadata_array_get_value_length(metadataArray, index).ToUInt64()]; Marshal.Copy(grpcsharp_metadata_array_get_value(metadataArray, index), bytes, 0, bytes.Length); - metadata.Add(new Metadata.Entry(key, bytes)); + metadata.Add(Metadata.Entry.CreateUnsafe(key, bytes)); } return metadata; } diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 688f9f6fec..59f4c5727c 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -67,7 +67,7 @@ namespace Grpc.Core.Internal var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, method.RequestMarshaller.Deserializer, - environment); + environment, newRpc.Server); asyncCall.Initialize(newRpc.Call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -123,7 +123,7 @@ namespace Grpc.Core.Internal var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, method.RequestMarshaller.Deserializer, - environment); + environment, newRpc.Server); asyncCall.Initialize(newRpc.Call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -179,7 +179,7 @@ namespace Grpc.Core.Internal var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, method.RequestMarshaller.Deserializer, - environment); + environment, newRpc.Server); asyncCall.Initialize(newRpc.Call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -239,7 +239,7 @@ namespace Grpc.Core.Internal var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, method.RequestMarshaller.Deserializer, - environment); + environment, newRpc.Server); asyncCall.Initialize(newRpc.Call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -278,7 +278,7 @@ namespace Grpc.Core.Internal { // We don't care about the payload type here. var asyncCall = new AsyncCallServer<byte[], byte[]>( - (payload) => payload, (payload) => payload, environment); + (payload) => payload, (payload) => payload, environment, newRpc.Server); asyncCall.Initialize(newRpc.Call); var finishedTask = asyncCall.ServerSideCallAsync(); diff --git a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs index 37a4f5256b..51e352a18b 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs @@ -37,7 +37,7 @@ using Grpc.Core.Utils; namespace Grpc.Core.Internal { /// <summary> - /// grpc_server_credentials from <grpc/grpc_security.h> + /// grpc_server_credentials from <c>grpc/grpc_security.h</c> /// </summary> internal class ServerCredentialsSafeHandle : SafeHandleZeroIsInvalid { diff --git a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs index f9b44b1acf..5ee7ac14e8 100644 --- a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs @@ -74,6 +74,9 @@ namespace Grpc.Core.Internal public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, ChannelArgsSafeHandle args) { + // Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle. + // Doing so would make object finalizer crash if we end up abandoning the handle. + GrpcEnvironment.GrpcNativeInit(); return grpcsharp_server_create(cq, args); } @@ -109,6 +112,7 @@ namespace Grpc.Core.Internal protected override bool ReleaseHandle() { grpcsharp_server_destroy(handle); + GrpcEnvironment.GrpcNativeShutdown(); return true; } diff --git a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs index 382481d871..35561d25d8 100644 --- a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs +++ b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs @@ -51,7 +51,19 @@ namespace Grpc.Core.Logging private ConsoleLogger(Type forType) { this.forType = forType; - this.forTypeString = forType != null ? forType.FullName + " " : ""; + if (forType != null) + { + var namespaceStr = forType.Namespace ?? ""; + if (namespaceStr.Length > 0) + { + namespaceStr += "."; + } + this.forTypeString = namespaceStr + forType.Name + " "; + } + else + { + this.forTypeString = ""; + } } /// <summary> diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs index 9db2abf46e..99fe0b5478 100644 --- a/src/csharp/Grpc.Core/Metadata.cs +++ b/src/csharp/Grpc.Core/Metadata.cs @@ -41,11 +41,22 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// Provides access to read and write metadata values to be exchanged during a call. + /// A collection of metadata entries that can be exchanged during a call. + /// gRPC supports these types of metadata: + /// <list type="bullet"> + /// <item><term>Request headers</term><description>are sent by the client at the beginning of a remote call before any request messages are sent.</description></item> + /// <item><term>Response headers</term><description>are sent by the server at the beginning of a remote call handler before any response messages are sent.</description></item> + /// <item><term>Response trailers</term><description>are sent by the server at the end of a remote call along with resulting call status.</description></item> + /// </list> /// </summary> public sealed class Metadata : IList<Metadata.Entry> { /// <summary> + /// All binary headers should have this suffix. + /// </summary> + public const string BinaryHeaderSuffix = "-bin"; + + /// <summary> /// An read-only instance of metadata containing no entries. /// </summary> public static readonly Metadata Empty = new Metadata().Freeze(); @@ -53,21 +64,19 @@ namespace Grpc.Core readonly List<Entry> entries; bool readOnly; + /// <summary> + /// Initializes a new instance of <c>Metadata</c>. + /// </summary> public Metadata() { this.entries = new List<Entry>(); } - public Metadata(ICollection<Entry> entries) - { - this.entries = new List<Entry>(entries); - } - /// <summary> /// Makes this object read-only. /// </summary> /// <returns>this object</returns> - public Metadata Freeze() + internal Metadata Freeze() { this.readOnly = true; return this; @@ -181,23 +190,49 @@ namespace Grpc.Core private static readonly Encoding Encoding = Encoding.ASCII; readonly string key; - string value; - byte[] valueBytes; + readonly string value; + readonly byte[] valueBytes; + + private Entry(string key, string value, byte[] valueBytes) + { + this.key = key; + this.value = value; + this.valueBytes = valueBytes; + } + /// <summary> + /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct with a binary value. + /// </summary> + /// <param name="key">Metadata key, needs to have suffix indicating a binary valued metadata entry.</param> + /// <param name="valueBytes">Value bytes.</param> public Entry(string key, byte[] valueBytes) { - this.key = Preconditions.CheckNotNull(key, "key"); + this.key = NormalizeKey(key); + Preconditions.CheckArgument(this.key.EndsWith(BinaryHeaderSuffix), + "Key for binary valued metadata entry needs to have suffix indicating binary value."); this.value = null; - this.valueBytes = Preconditions.CheckNotNull(valueBytes, "valueBytes"); + Preconditions.CheckNotNull(valueBytes, "valueBytes"); + this.valueBytes = new byte[valueBytes.Length]; + Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length); // defensive copy to guarantee immutability } + /// <summary> + /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct holding an ASCII value. + /// </summary> + /// <param name="key">Metadata key, must not use suffix indicating a binary valued metadata entry.</param> + /// <param name="value">Value string. Only ASCII characters are allowed.</param> public Entry(string key, string value) { - this.key = Preconditions.CheckNotNull(key, "key"); + this.key = NormalizeKey(key); + Preconditions.CheckArgument(!this.key.EndsWith(BinaryHeaderSuffix), + "Key for ASCII valued metadata entry cannot have suffix indicating binary value."); this.value = Preconditions.CheckNotNull(value, "value"); this.valueBytes = null; } + /// <summary> + /// Gets the metadata entry key. + /// </summary> public string Key { get @@ -206,33 +241,86 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the binary value of this metadata entry. + /// </summary> public byte[] ValueBytes { get { if (valueBytes == null) { - valueBytes = Encoding.GetBytes(value); + return Encoding.GetBytes(value); } - return valueBytes; + + // defensive copy to guarantee immutability + var bytes = new byte[valueBytes.Length]; + Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length); + return bytes; } } + /// <summary> + /// Gets the string value of this metadata entry. + /// </summary> public string Value { get { - if (value == null) - { - value = Encoding.GetString(valueBytes); - } - return value; + Preconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry"); + return value ?? Encoding.GetString(valueBytes); } } - + + /// <summary> + /// Returns <c>true</c> if this entry is a binary-value entry. + /// </summary> + public bool IsBinary + { + get + { + return value == null; + } + } + + /// <summary> + /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Metadata.Entry"/>. + /// </summary> public override string ToString() { - return string.Format("[Entry: key={0}, value={1}]", Key, Value); + if (IsBinary) + { + return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes); + } + + return string.Format("[Entry: key={0}, value={1}]", key, value); + } + + /// <summary> + /// Gets the serialized value for this entry. For binary metadata entries, this leaks + /// the internal <c>valueBytes</c> byte array and caller must not change contents of it. + /// </summary> + internal byte[] GetSerializedValueUnsafe() + { + return valueBytes ?? Encoding.GetBytes(value); + } + + /// <summary> + /// Creates a binary value or ascii value metadata entry from data received from the native layer. + /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying. + /// </summary> + internal static Entry CreateUnsafe(string key, byte[] valueBytes) + { + if (key.EndsWith(BinaryHeaderSuffix)) + { + return new Entry(key, null, valueBytes); + } + return new Entry(key, Encoding.GetString(valueBytes), null); + } + + private static string NormalizeKey(string key) + { + return Preconditions.CheckNotNull(key, "key").ToLower(); } } } diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs index 4c208b4a26..99162a7d5d 100644 --- a/src/csharp/Grpc.Core/Method.cs +++ b/src/csharp/Grpc.Core/Method.cs @@ -55,9 +55,38 @@ namespace Grpc.Core } /// <summary> + /// A non-generic representation of a remote method. + /// </summary> + public interface IMethod + { + /// <summary> + /// Gets the type of the method. + /// </summary> + MethodType Type { get; } + + /// <summary> + /// Gets the name of the service to which this method belongs. + /// </summary> + string ServiceName { get; } + + /// <summary> + /// Gets the unqualified name of the method. + /// </summary> + string Name { get; } + + /// <summary> + /// Gets the fully qualified name of the method. On the server side, methods are dispatched + /// based on this name. + /// </summary> + string FullName { get; } + } + + /// <summary> /// A description of a remote method. /// </summary> - public class Method<TRequest, TResponse> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> + public class Method<TRequest, TResponse> : IMethod { readonly MethodType type; readonly string serviceName; diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs index c76f126026..7c94d21561 100644 --- a/src/csharp/Grpc.Core/Server.cs +++ b/src/csharp/Grpc.Core/Server.cs @@ -44,12 +44,14 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// A gRPC server. + /// gRPC server. A single server can server arbitrary number of services and can listen on more than one ports. /// </summary> public class Server { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>(); + readonly AtomicCounter activeCallCounter = new AtomicCounter(); + readonly ServiceDefinitionCollection serviceDefinitions; readonly ServerPortCollection ports; readonly GrpcEnvironment environment; @@ -73,7 +75,7 @@ namespace Grpc.Core { this.serviceDefinitions = new ServiceDefinitionCollection(this); this.ports = new ServerPortCollection(this); - this.environment = GrpcEnvironment.GetInstance(); + this.environment = GrpcEnvironment.AddRef(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options)) { @@ -106,6 +108,17 @@ namespace Grpc.Core } /// <summary> + /// To allow awaiting termination of the server. + /// </summary> + public Task ShutdownTask + { + get + { + return shutdownTcs.Task; + } + } + + /// <summary> /// Starts the server. /// </summary> public void Start() @@ -136,18 +149,9 @@ namespace Grpc.Core handle.ShutdownAndNotify(HandleServerShutdown, environment); await shutdownTcs.Task; - handle.Dispose(); - } + DisposeHandle(); - /// <summary> - /// To allow awaiting termination of the server. - /// </summary> - public Task ShutdownTask - { - get - { - return shutdownTcs.Task; - } + await Task.Run(() => GrpcEnvironment.Release()); } /// <summary> @@ -166,7 +170,22 @@ namespace Grpc.Core handle.ShutdownAndNotify(HandleServerShutdown, environment); handle.CancelAllCalls(); await shutdownTcs.Task; - handle.Dispose(); + DisposeHandle(); + } + + internal void AddCallReference(object call) + { + activeCallCounter.Increment(); + + bool success = false; + handle.DangerousAddRef(ref success); + Preconditions.CheckState(success); + } + + internal void RemoveCallReference(object call) + { + handle.DangerousRelease(); + activeCallCounter.Decrement(); } /// <summary> @@ -227,6 +246,16 @@ namespace Grpc.Core } } + private void DisposeHandle() + { + var activeCallCount = activeCallCounter.Count; + if (activeCallCount > 0) + { + Logger.Warning("Server shutdown has finished but there are still {0} active calls for that server.", activeCallCount); + } + handle.Dispose(); + } + /// <summary> /// Selects corresponding handler for given call and handles the call. /// </summary> @@ -254,7 +283,7 @@ namespace Grpc.Core { if (success) { - ServerRpcNew newRpc = ctx.GetServerRpcNew(); + ServerRpcNew newRpc = ctx.GetServerRpcNew(this); // after server shutdown, the callback returns with null call if (!newRpc.Call.IsInvalid) @@ -295,6 +324,9 @@ namespace Grpc.Core server.AddServiceDefinitionInternal(serviceDefinition); } + /// <summary> + /// Gets enumerator for this collection. + /// </summary> public IEnumerator<ServerServiceDefinition> GetEnumerator() { return server.serviceDefinitionsList.GetEnumerator(); @@ -340,6 +372,9 @@ namespace Grpc.Core return Add(new ServerPort(host, port, credentials)); } + /// <summary> + /// Gets enumerator for this collection. + /// </summary> public IEnumerator<ServerPort> GetEnumerator() { return server.serverPortList.GetEnumerator(); diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs index 75d81c64f3..09a6b882a6 100644 --- a/src/csharp/Grpc.Core/ServerCallContext.cs +++ b/src/csharp/Grpc.Core/ServerCallContext.cs @@ -72,6 +72,13 @@ namespace Grpc.Core this.writeOptionsHolder = writeOptionsHolder; } + /// <summary> + /// Asynchronously sends response headers for the current call to the client. This method may only be invoked once for each call and needs to be invoked + /// before any response messages are written. Writing the first response message implicitly sends empty response headers if <c>WriteResponseHeadersAsync</c> haven't + /// been called yet. + /// </summary> + /// <param name="responseHeaders">The response headers to send.</param> + /// <returns>The task that finished once response headers have been written.</returns> public Task WriteResponseHeadersAsync(Metadata responseHeaders) { return writeHeadersFunc(responseHeaders); @@ -186,6 +193,9 @@ namespace Grpc.Core /// </summary> public interface IHasWriteOptions { + /// <summary> + /// Gets or sets the write options. + /// </summary> WriteOptions WriteOptions { get; set; } } } diff --git a/src/csharp/Grpc.Core/ServerMethods.cs b/src/csharp/Grpc.Core/ServerMethods.cs index 1f119a80ff..728f77cde5 100644 --- a/src/csharp/Grpc.Core/ServerMethods.cs +++ b/src/csharp/Grpc.Core/ServerMethods.cs @@ -38,6 +38,8 @@ namespace Grpc.Core /// <summary> /// Server-side handler for unary call. /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> public delegate Task<TResponse> UnaryServerMethod<TRequest, TResponse>(TRequest request, ServerCallContext context) where TRequest : class where TResponse : class; @@ -45,6 +47,8 @@ namespace Grpc.Core /// <summary> /// Server-side handler for client streaming call. /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> public delegate Task<TResponse> ClientStreamingServerMethod<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, ServerCallContext context) where TRequest : class where TResponse : class; @@ -52,6 +56,8 @@ namespace Grpc.Core /// <summary> /// Server-side handler for server streaming call. /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> public delegate Task ServerStreamingServerMethod<TRequest, TResponse>(TRequest request, IServerStreamWriter<TResponse> responseStream, ServerCallContext context) where TRequest : class where TResponse : class; @@ -59,6 +65,8 @@ namespace Grpc.Core /// <summary> /// Server-side handler for bidi streaming call. /// </summary> + /// <typeparam name="TRequest">Request message type for this method.</typeparam> + /// <typeparam name="TResponse">Response message type for this method.</typeparam> public delegate Task DuplexStreamingServerMethod<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, IServerStreamWriter<TResponse> responseStream, ServerCallContext context) where TRequest : class where TResponse : class; diff --git a/src/csharp/Grpc.Core/ServerServiceDefinition.cs b/src/csharp/Grpc.Core/ServerServiceDefinition.cs index 94b0a320c3..deb1431ca3 100644 --- a/src/csharp/Grpc.Core/ServerServiceDefinition.cs +++ b/src/csharp/Grpc.Core/ServerServiceDefinition.cs @@ -40,6 +40,8 @@ namespace Grpc.Core { /// <summary> /// Mapping of method names to server call handlers. + /// Normally, the <c>ServerServiceDefinition</c> objects will be created by the <c>BindService</c> factory method + /// that is part of the autogenerated code for a protocol buffers service definition. /// </summary> public class ServerServiceDefinition { @@ -58,21 +60,41 @@ 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) { return new Builder(serviceName); } + /// <summary> + /// Builder class for <see cref="ServerServiceDefinition"/>. + /// </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) { this.serviceName = serviceName; } + /// <summary> + /// Adds a definitions for a single request - single response method. + /// </summary> + /// <typeparam name="TRequest">The request message class.</typeparam> + /// <typeparam name="TResponse">The response message class.</typeparam> + /// <param name="method">The method.</param> + /// <param name="handler">The method handler.</param> + /// <returns>This builder instance.</returns> public Builder AddMethod<TRequest, TResponse>( Method<TRequest, TResponse> method, UnaryServerMethod<TRequest, TResponse> handler) @@ -83,6 +105,14 @@ namespace Grpc.Core return this; } + /// <summary> + /// Adds a definitions for a client streaming method. + /// </summary> + /// <typeparam name="TRequest">The request message class.</typeparam> + /// <typeparam name="TResponse">The response message class.</typeparam> + /// <param name="method">The method.</param> + /// <param name="handler">The method handler.</param> + /// <returns>This builder instance.</returns> public Builder AddMethod<TRequest, TResponse>( Method<TRequest, TResponse> method, ClientStreamingServerMethod<TRequest, TResponse> handler) @@ -93,6 +123,14 @@ namespace Grpc.Core return this; } + /// <summary> + /// Adds a definitions for a server streaming method. + /// </summary> + /// <typeparam name="TRequest">The request message class.</typeparam> + /// <typeparam name="TResponse">The response message class.</typeparam> + /// <param name="method">The method.</param> + /// <param name="handler">The method handler.</param> + /// <returns>This builder instance.</returns> public Builder AddMethod<TRequest, TResponse>( Method<TRequest, TResponse> method, ServerStreamingServerMethod<TRequest, TResponse> handler) @@ -103,6 +141,14 @@ namespace Grpc.Core return this; } + /// <summary> + /// Adds a definitions for a bidirectional streaming method. + /// </summary> + /// <typeparam name="TRequest">The request message class.</typeparam> + /// <typeparam name="TResponse">The response message class.</typeparam> + /// <param name="method">The method.</param> + /// <param name="handler">The method handler.</param> + /// <returns>This builder instance.</returns> public Builder AddMethod<TRequest, TResponse>( Method<TRequest, TResponse> method, DuplexStreamingServerMethod<TRequest, TResponse> handler) @@ -113,6 +159,10 @@ namespace Grpc.Core return this; } + /// <summary> + /// Creates an immutable <c>ServerServiceDefinition</c> from this builder. + /// </summary> + /// <returns>The <c>ServerServiceDefinition</c> object.</returns> public ServerServiceDefinition Build() { return new ServerServiceDefinition(callHandlers); diff --git a/src/csharp/Grpc.Core/Utils/Preconditions.cs b/src/csharp/Grpc.Core/Utils/Preconditions.cs index 374262f87a..a8ab603391 100644 --- a/src/csharp/Grpc.Core/Utils/Preconditions.cs +++ b/src/csharp/Grpc.Core/Utils/Preconditions.cs @@ -43,6 +43,7 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="ArgumentException"/> if condition is false. /// </summary> + /// <param name="condition">The condition.</param> public static void CheckArgument(bool condition) { if (!condition) @@ -54,6 +55,8 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="ArgumentException"/> with given message if condition is false. /// </summary> + /// <param name="condition">The condition.</param> + /// <param name="errorMessage">The error message.</param> public static void CheckArgument(bool condition, string errorMessage) { if (!condition) @@ -65,6 +68,7 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="ArgumentNullException"/> if reference is null. /// </summary> + /// <param name="reference">The reference.</param> public static T CheckNotNull<T>(T reference) { if (reference == null) @@ -77,6 +81,8 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="ArgumentNullException"/> if reference is null. /// </summary> + /// <param name="reference">The reference.</param> + /// <param name="paramName">The parameter name.</param> public static T CheckNotNull<T>(T reference, string paramName) { if (reference == null) @@ -89,6 +95,7 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="InvalidOperationException"/> if condition is false. /// </summary> + /// <param name="condition">The condition.</param> public static void CheckState(bool condition) { if (!condition) @@ -100,6 +107,8 @@ namespace Grpc.Core.Utils /// <summary> /// Throws <see cref="InvalidOperationException"/> with given message if condition is false. /// </summary> + /// <param name="condition">The condition.</param> + /// <param name="errorMessage">The error message.</param> public static void CheckState(bool condition, string errorMessage) { if (!condition) diff --git a/src/csharp/Grpc.Core/WriteOptions.cs b/src/csharp/Grpc.Core/WriteOptions.cs index 7ef3189d76..7523ada84a 100644 --- a/src/csharp/Grpc.Core/WriteOptions.cs +++ b/src/csharp/Grpc.Core/WriteOptions.cs @@ -66,11 +66,18 @@ namespace Grpc.Core private WriteFlags flags; + /// <summary> + /// Initializes a new instance of <c>WriteOptions</c> class. + /// </summary> + /// <param name="flags">The write flags.</param> public WriteOptions(WriteFlags flags = default(WriteFlags)) { this.flags = flags; } + /// <summary> + /// Gets the write flags. + /// </summary> public WriteFlags Flags { get |