From 9698b5b29f84707ca590c2f618c8f5d00a921da7 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 10 Aug 2015 16:40:19 -0700 Subject: [PATCH] polishing CallOptions --- .../Grpc.Core.Tests/ContextPropagationTest.cs | 31 +++++++ .../Grpc.Core.Tests/MockServiceHelper.cs | 12 +-- src/csharp/Grpc.Core/CallInvocationDetails.cs | 24 +++++- src/csharp/Grpc.Core/CallOptions.cs | 86 ++++++++++++++++--- src/csharp/Grpc.Core/ClientBase.cs | 18 +++- .../Grpc.Core/ContextPropagationToken.cs | 58 ++++++++++--- src/csharp/Grpc.Core/Internal/AsyncCall.cs | 7 +- 7 files changed, 193 insertions(+), 43 deletions(-) diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs index a7f5075874d..db5f953b0e1 100644 --- a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs +++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs @@ -110,6 +110,14 @@ namespace Grpc.Core.Tests helper.ClientStreamingHandler = new ClientStreamingServerMethod(async (requestStream, context) => { + Assert.Throws(typeof(ArgumentException), () => + { + // Trying to override deadline while propagating deadline from parent call will throw. + Calls.BlockingUnaryCall(helper.CreateUnaryCall( + new CallOptions(deadline: DateTime.UtcNow.AddDays(8), + propagationToken: context.CreatePropagationToken())), ""); + }); + var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken()); return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); }); @@ -118,5 +126,28 @@ namespace Grpc.Core.Tests await call.RequestStream.CompleteAsync(); Assert.AreEqual("PASS", await call); } + + [Test] + public async Task SuppressDeadlinePropagation() + { + helper.UnaryHandler = new UnaryServerMethod(async (request, context) => + { + Assert.AreEqual(DateTime.MaxValue, context.Deadline); + return "PASS"; + }); + + helper.ClientStreamingHandler = new ClientStreamingServerMethod(async (requestStream, context) => + { + Assert.IsTrue(context.CancellationToken.CanBeCanceled); + + var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken(new ContextPropagationOptions(propagateDeadline: false))); + return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + }); + + var cts = new CancellationTokenSource(); + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddDays(7)))); + await call.RequestStream.CompleteAsync(); + Assert.AreEqual("PASS", await call); + } } } diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs index b642286b116..bb69648d8bf 100644 --- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs +++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs @@ -153,27 +153,23 @@ namespace Grpc.Core.Tests return channel; } - public CallInvocationDetails CreateUnaryCall(CallOptions options = null) + public CallInvocationDetails CreateUnaryCall(CallOptions options = default(CallOptions)) { - options = options ?? new CallOptions(); return new CallInvocationDetails(channel, UnaryMethod, options); } - public CallInvocationDetails CreateClientStreamingCall(CallOptions options = null) + public CallInvocationDetails CreateClientStreamingCall(CallOptions options = default(CallOptions)) { - options = options ?? new CallOptions(); return new CallInvocationDetails(channel, ClientStreamingMethod, options); } - public CallInvocationDetails CreateServerStreamingCall(CallOptions options = null) + public CallInvocationDetails CreateServerStreamingCall(CallOptions options = default(CallOptions)) { - options = options ?? new CallOptions(); return new CallInvocationDetails(channel, ServerStreamingMethod, options); } - public CallInvocationDetails CreateDuplexStreamingCall(CallOptions options = null) + public CallInvocationDetails CreateDuplexStreamingCall(CallOptions options = default(CallOptions)) { - options = options ?? new CallOptions(); return new CallInvocationDetails(channel, DuplexStreamingMethod, options); } diff --git a/src/csharp/Grpc.Core/CallInvocationDetails.cs b/src/csharp/Grpc.Core/CallInvocationDetails.cs index cca78aca0ff..8959baf306b 100644 --- a/src/csharp/Grpc.Core/CallInvocationDetails.cs +++ b/src/csharp/Grpc.Core/CallInvocationDetails.cs @@ -40,17 +40,22 @@ namespace Grpc.Core /// /// Details about a client-side call to be invoked. /// - public class CallInvocationDetails + public struct CallInvocationDetails { readonly Channel channel; readonly string method; readonly string host; readonly Marshaller requestMarshaller; readonly Marshaller responseMarshaller; - readonly CallOptions options; + CallOptions options; public CallInvocationDetails(Channel channel, Method method, CallOptions options) : - this(channel, method.FullName, null, method.RequestMarshaller, method.ResponseMarshaller, options) + this(channel, method, null, options) + { + } + + public CallInvocationDetails(Channel channel, Method method, string host, CallOptions options) : + this(channel, method.FullName, host, method.RequestMarshaller, method.ResponseMarshaller, options) { } @@ -61,7 +66,7 @@ namespace Grpc.Core this.host = host; this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller, "requestMarshaller"); this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller, "responseMarshaller"); - this.options = Preconditions.CheckNotNull(options, "options"); + this.options = options; } public Channel Channel @@ -111,5 +116,16 @@ namespace Grpc.Core return options; } } + + /// + /// Returns new instance of with + /// Options set to the value provided. Values of all other fields are preserved. + /// + public CallInvocationDetails WithOptions(CallOptions options) + { + var newDetails = this; + newDetails.options = options; + return newDetails; + } } } diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs index 0d82b5a28ee..3dfe80b48ce 100644 --- a/src/csharp/Grpc.Core/CallOptions.cs +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -42,29 +42,28 @@ namespace Grpc.Core /// /// Options for calls made by client. /// - public class CallOptions + public struct CallOptions { - readonly Metadata headers; - readonly DateTime deadline; - readonly CancellationToken cancellationToken; - readonly WriteOptions writeOptions; - readonly ContextPropagationToken propagationToken; + Metadata headers; + DateTime? deadline; + CancellationToken cancellationToken; + WriteOptions writeOptions; + ContextPropagationToken propagationToken; /// - /// Creates a new instance of CallOptions. + /// Creates a new instance of CallOptions struct. /// /// Headers to be sent with the call. /// Deadline for the call to finish. null means no deadline. /// Can be used to request cancellation of the call. /// Write options that will be used for this call. /// Context propagation token obtained from . - public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken? cancellationToken = null, + public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken), WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null) { - // TODO(jtattermusch): consider only creating metadata object once it's really needed. - this.headers = headers ?? new Metadata(); - this.deadline = deadline ?? (propagationToken != null ? propagationToken.Deadline : DateTime.MaxValue); - this.cancellationToken = cancellationToken ?? (propagationToken != null ? propagationToken.CancellationToken : CancellationToken.None); + this.headers = headers; + this.deadline = deadline; + this.cancellationToken = cancellationToken; this.writeOptions = writeOptions; this.propagationToken = propagationToken; } @@ -80,7 +79,7 @@ namespace Grpc.Core /// /// Call deadline. /// - public DateTime Deadline + public DateTime? Deadline { get { return deadline; } } @@ -114,5 +113,66 @@ namespace Grpc.Core return this.propagationToken; } } + + /// + /// Returns new instance of with + /// Headers set to the value provided. Values of all other fields are preserved. + /// + public CallOptions WithHeaders(Metadata headers) + { + var newOptions = this; + newOptions.headers = headers; + return newOptions; + } + + /// + /// Returns new instance of with + /// Deadline set to the value provided. Values of all other fields are preserved. + /// + public CallOptions WithDeadline(DateTime deadline) + { + var newOptions = this; + newOptions.deadline = deadline; + return newOptions; + } + + /// + /// Returns new instance of with + /// CancellationToken set to the value provided. Values of all other fields are preserved. + /// + public CallOptions WithCancellationToken(CancellationToken cancellationToken) + { + var newOptions = this; + newOptions.cancellationToken = cancellationToken; + return newOptions; + } + + /// + /// Returns a new instance of with + /// all previously unset values set to their defaults and deadline and cancellation + /// token propagated when appropriate. + /// + internal CallOptions Normalize() + { + var newOptions = this; + if (propagationToken != null) + { + if (propagationToken.Options.IsPropagateDeadline) + { + Preconditions.CheckArgument(!newOptions.deadline.HasValue, + "Cannot propagate deadline from parent call. The deadline has already been set explicitly."); + newOptions.deadline = propagationToken.ParentDeadline; + } + if (propagationToken.Options.IsPropagateCancellation) + { + Preconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled, + "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value."); + } + } + + newOptions.headers = newOptions.headers ?? Metadata.Empty; + newOptions.deadline = newOptions.deadline ?? DateTime.MaxValue; + return newOptions; + } } } diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index 88494bb4ac8..48fc7ed34a5 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -62,6 +62,18 @@ namespace Grpc.Core set; } + /// + /// gRPC supports multiple "hosts" being served by a single server. + /// This property can be used to set the target host explicitly. + /// By default, this will be set to null with the meaning + /// "use default host". + /// + public string Host + { + get; + set; + } + /// /// Channel associated with this client. /// @@ -83,10 +95,14 @@ namespace Grpc.Core var interceptor = HeaderInterceptor; if (interceptor != null) { + if (options.Headers == null) + { + options = options.WithHeaders(new Metadata()); + } interceptor(options.Headers); options.Headers.Freeze(); } - return new CallInvocationDetails(channel, method, options); + return new CallInvocationDetails(channel, method, Host, options); } } } diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs index b6ea5115a4f..2e4bfc9e477 100644 --- a/src/csharp/Grpc.Core/ContextPropagationToken.cs +++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs @@ -52,7 +52,7 @@ namespace Grpc.Core /// /// Default propagation mask used by C core. /// - const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff; + private const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff; /// /// Default propagation mask used by C# - we want to propagate deadline @@ -74,6 +74,9 @@ namespace Grpc.Core this.options = options ?? ContextPropagationOptions.Default; } + /// + /// Gets the native handle of the parent call. + /// internal CallSafeHandle ParentCall { get @@ -82,7 +85,10 @@ namespace Grpc.Core } } - internal DateTime Deadline + /// + /// Gets the parent call's deadline. + /// + internal DateTime ParentDeadline { get { @@ -90,7 +96,10 @@ namespace Grpc.Core } } - internal CancellationToken CancellationToken + /// + /// Gets the parent call's cancellation token. + /// + internal CancellationToken ParentCancellationToken { get { @@ -98,6 +107,9 @@ namespace Grpc.Core } } + /// + /// Get the context propagation options. + /// internal ContextPropagationOptions Options { get @@ -105,16 +117,6 @@ namespace Grpc.Core return this.options; } } - - internal bool IsPropagateDeadline - { - get { return false; } - } - - internal bool IsPropagateCancellation - { - get { return false; } - } } /// @@ -122,7 +124,37 @@ namespace Grpc.Core /// public class ContextPropagationOptions { + /// + /// The context propagation options that will be used by default. + /// public static readonly ContextPropagationOptions Default = new ContextPropagationOptions(); + + bool propagateDeadline; + bool propagateCancellation; + + + /// + /// Creates new context propagation options. + /// + /// If set to true parent call's deadline will be propagated to the child call. + /// If set to true parent call's cancellation token will be propagated to the child call. + public ContextPropagationOptions(bool propagateDeadline = true, bool propagateCancellation = true) + { + this.propagateDeadline = propagateDeadline; + this.propagateCancellation = propagateCancellation; + } + + /// true if parent call's deadline should be propagated to the child call. + public bool IsPropagateDeadline + { + get { return this.propagateDeadline; } + } + + /// true if parent call's cancellation token should be propagated to the child call. + public bool IsPropagateCancellation + { + get { return this.propagateCancellation; } + } } /// diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index 6aeca29d964..2c3e3d75eae 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -63,7 +63,7 @@ namespace Grpc.Core.Internal public AsyncCall(CallInvocationDetails callDetails) : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer) { - this.details = callDetails; + this.details = callDetails.WithOptions(callDetails.Options.Normalize()); this.initialMetadataSent = true; // we always send metadata at the very beginning of the call. } @@ -318,12 +318,11 @@ namespace Grpc.Core.Internal private void Initialize(CompletionQueueSafeHandle cq) { - var propagationToken = details.Options.PropagationToken; - var parentCall = propagationToken != null ? propagationToken.ParentCall : CallSafeHandle.NullInstance; + var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry, parentCall, ContextPropagationToken.DefaultMask, cq, - details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline)); + details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); details.Channel.Environment.DebugStats.ActiveClientCalls.Increment(); InitializeInternal(call); RegisterCancellationCallback();