Merge pull request #17766 from jtattermusch/context_propagation_token_refactor

Refactor ContextPropagationToken to allow moving to Grpc.Core.Api package
pull/17820/head
Jan Tattermusch 6 years ago committed by GitHub
commit f0bfcd864c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/csharp/Grpc.Core.Tests/CallOptionsTest.cs
  2. 25
      src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
  3. 12
      src/csharp/Grpc.Core/CallOptions.cs
  4. 59
      src/csharp/Grpc.Core/ContextPropagationOptions.cs
  5. 128
      src/csharp/Grpc.Core/ContextPropagationToken.cs
  6. 4
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  7. 34
      src/csharp/Grpc.Core/Internal/ContextPropagationFlags.cs
  8. 119
      src/csharp/Grpc.Core/Internal/ContextPropagationTokenImpl.cs
  9. 2
      src/csharp/Grpc.Core/Internal/DefaultServerCallContext.cs

@ -45,7 +45,7 @@ namespace Grpc.Core.Tests
var writeOptions = new WriteOptions();
Assert.AreSame(writeOptions, options.WithWriteOptions(writeOptions).WriteOptions);
var propagationToken = new ContextPropagationToken(CallSafeHandle.NullInstance, DateTime.UtcNow,
var propagationToken = new ContextPropagationTokenImpl(CallSafeHandle.NullInstance, DateTime.UtcNow,
CancellationToken.None, ContextPropagationOptions.Default);
Assert.AreSame(propagationToken, options.WithPropagationToken(propagationToken).PropagationToken);
@ -72,13 +72,13 @@ namespace Grpc.Core.Tests
Assert.AreEqual(DateTime.MaxValue, new CallOptions().Normalize().Deadline.Value);
var deadline = DateTime.UtcNow;
var propagationToken1 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, CancellationToken.None,
var propagationToken1 = new ContextPropagationTokenImpl(CallSafeHandle.NullInstance, deadline, CancellationToken.None,
new ContextPropagationOptions(propagateDeadline: true, propagateCancellation: false));
Assert.AreEqual(deadline, new CallOptions(propagationToken: propagationToken1).Normalize().Deadline.Value);
Assert.Throws(typeof(ArgumentException), () => new CallOptions(deadline: deadline, propagationToken: propagationToken1).Normalize());
var token = new CancellationTokenSource().Token;
var propagationToken2 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, token,
var propagationToken2 = new ContextPropagationTokenImpl(CallSafeHandle.NullInstance, deadline, token,
new ContextPropagationOptions(propagateDeadline: false, propagateCancellation: true));
Assert.AreEqual(token, new CallOptions(propagationToken: propagationToken2).Normalize().CancellationToken);
Assert.Throws(typeof(ArgumentException), () => new CallOptions(cancellationToken: token, propagationToken: propagationToken2).Normalize());

@ -72,7 +72,7 @@ namespace Grpc.Core.Tests
helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
{
var propagationToken = context.CreatePropagationToken();
Assert.IsNotNull(propagationToken.ParentCall);
Assert.IsNotNull(propagationToken.AsImplOrNull().ParentCall);
var callOptions = new CallOptions(propagationToken: propagationToken);
try
@ -154,5 +154,28 @@ namespace Grpc.Core.Tests
await call.RequestStream.CompleteAsync();
Assert.AreEqual("PASS", await call);
}
[Test]
public void ForeignPropagationTokenInterpretedAsNull()
{
Assert.IsNull(new ForeignContextPropagationToken().AsImplOrNull());
}
[Test]
public async Task ForeignPropagationTokenIsIgnored()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
{
return Task.FromResult("PASS");
});
var callOptions = new CallOptions(propagationToken: new ForeignContextPropagationToken());
await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
}
// For testing, represents context propagation token that's not generated by Grpc.Core
private class ForeignContextPropagationToken : ContextPropagationToken
{
}
}
}

@ -236,19 +236,21 @@ namespace Grpc.Core
internal CallOptions Normalize()
{
var newOptions = this;
if (propagationToken != null)
// silently ignore the context propagation token if it wasn't produced by "us"
var propagationTokenImpl = propagationToken.AsImplOrNull();
if (propagationTokenImpl != null)
{
if (propagationToken.Options.IsPropagateDeadline)
if (propagationTokenImpl.Options.IsPropagateDeadline)
{
GrpcPreconditions.CheckArgument(!newOptions.deadline.HasValue,
"Cannot propagate deadline from parent call. The deadline has already been set explicitly.");
newOptions.deadline = propagationToken.ParentDeadline;
newOptions.deadline = propagationTokenImpl.ParentDeadline;
}
if (propagationToken.Options.IsPropagateCancellation)
if (propagationTokenImpl.Options.IsPropagateCancellation)
{
GrpcPreconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled,
"Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value.");
newOptions.cancellationToken = propagationToken.ParentCancellationToken;
newOptions.cancellationToken = propagationTokenImpl.ParentCancellationToken;
}
}

@ -0,0 +1,59 @@
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
namespace Grpc.Core
{
/// <summary>
/// Options for <see cref="ContextPropagationToken"/>.
/// </summary>
public class ContextPropagationOptions
{
/// <summary>
/// The context propagation options that will be used by default.
/// </summary>
public static readonly ContextPropagationOptions Default = new ContextPropagationOptions();
bool propagateDeadline;
bool propagateCancellation;
/// <summary>
/// Creates new context propagation options.
/// </summary>
/// <param name="propagateDeadline">If set to <c>true</c> parent call's deadline will be propagated to the child call.</param>
/// <param name="propagateCancellation">If set to <c>true</c> parent call's cancellation token will be propagated to the child call.</param>
public ContextPropagationOptions(bool propagateDeadline = true, bool propagateCancellation = true)
{
this.propagateDeadline = propagateDeadline;
this.propagateCancellation = propagateCancellation;
}
/// <summary><c>true</c> if parent call's deadline should be propagated to the child call.</summary>
public bool IsPropagateDeadline
{
get { return this.propagateDeadline; }
}
/// <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; }
}
}
}

@ -16,12 +16,6 @@
#endregion
using System;
using System.Threading;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
@ -29,127 +23,13 @@ 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.
/// 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.
/// Underlying gRPC implementation may provide other "opaque" contexts (like tracing context) that
/// are not explicitly accesible via the public C# API, but this token still allows propagating them.
/// </summary>
public class ContextPropagationToken
public abstract class ContextPropagationToken
{
/// <summary>
/// Default propagation mask used by C core.
/// </summary>
private const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff;
/// <summary>
/// Default propagation mask used by C# - we want to propagate deadline
/// and cancellation token by our own means.
/// </summary>
internal const ContextPropagationFlags DefaultMask = DefaultCoreMask
& ~ContextPropagationFlags.Deadline & ~ContextPropagationFlags.Cancellation;
readonly CallSafeHandle parentCall;
readonly DateTime deadline;
readonly CancellationToken cancellationToken;
readonly ContextPropagationOptions options;
internal ContextPropagationToken(CallSafeHandle parentCall, DateTime deadline, CancellationToken cancellationToken, ContextPropagationOptions options)
internal ContextPropagationToken()
{
this.parentCall = GrpcPreconditions.CheckNotNull(parentCall);
this.deadline = deadline;
this.cancellationToken = cancellationToken;
this.options = options ?? ContextPropagationOptions.Default;
}
/// <summary>
/// Gets the native handle of the parent call.
/// </summary>
internal CallSafeHandle ParentCall
{
get
{
return this.parentCall;
}
}
/// <summary>
/// Gets the parent call's deadline.
/// </summary>
internal DateTime ParentDeadline
{
get
{
return this.deadline;
}
}
/// <summary>
/// Gets the parent call's cancellation token.
/// </summary>
internal CancellationToken ParentCancellationToken
{
get
{
return this.cancellationToken;
}
}
/// <summary>
/// Get the context propagation options.
/// </summary>
internal ContextPropagationOptions Options
{
get
{
return this.options;
}
}
}
/// <summary>
/// Options for <see cref="ContextPropagationToken"/>.
/// </summary>
public class ContextPropagationOptions
{
/// <summary>
/// The context propagation options that will be used by default.
/// </summary>
public static readonly ContextPropagationOptions Default = new ContextPropagationOptions();
bool propagateDeadline;
bool propagateCancellation;
/// <summary>
/// Creates new context propagation options.
/// </summary>
/// <param name="propagateDeadline">If set to <c>true</c> parent call's deadline will be propagated to the child call.</param>
/// <param name="propagateCancellation">If set to <c>true</c> parent call's cancellation token will be propagated to the child call.</param>
public ContextPropagationOptions(bool propagateDeadline = true, bool propagateCancellation = true)
{
this.propagateDeadline = propagateDeadline;
this.propagateCancellation = propagateCancellation;
}
/// <summary><c>true</c> if parent call's deadline should be propagated to the child call.</summary>
public bool IsPropagateDeadline
{
get { return this.propagateDeadline; }
}
/// <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; }
}
}
/// <summary>
/// Context propagation flags from grpc/grpc.h.
/// </summary>
[Flags]
internal enum ContextPropagationFlags
{
Deadline = 1,
CensusStatsContext = 2,
CensusTracingContext = 4,
Cancellation = 8
}
}

@ -494,13 +494,13 @@ namespace Grpc.Core.Internal
return injectedNativeCall; // allows injecting a mock INativeCall in tests.
}
var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
var parentCall = details.Options.PropagationToken.AsImplOrNull()?.ParentCall ?? CallSafeHandle.NullInstance;
var credentials = details.Options.Credentials;
using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null)
{
var result = details.Channel.Handle.CreateCall(
parentCall, ContextPropagationToken.DefaultMask, cq,
parentCall, ContextPropagationTokenImpl.DefaultMask, cq,
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value), nativeCredentials);
return result;
}

@ -0,0 +1,34 @@
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
namespace Grpc.Core.Internal
{
/// <summary>
/// Context propagation flags from grpc/grpc.h.
/// </summary>
[Flags]
internal enum ContextPropagationFlags
{
Deadline = 1,
CensusStatsContext = 2,
CensusTracingContext = 4,
Cancellation = 8
}
}

@ -0,0 +1,119 @@
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.Threading;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
{
/// <summary>
/// Implementation of <c>ContextPropagationToken</c> that carries
/// all fields needed for context propagation by C-core based implementation of gRPC.
/// Instances of <c>ContextPropagationToken</c> that are not of this
/// type will be recognized as "foreign" and will be silently ignored
/// (treated as if null).
/// </summary>
internal class ContextPropagationTokenImpl : ContextPropagationToken
{
/// <summary>
/// Default propagation mask used by C core.
/// </summary>
private const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff;
/// <summary>
/// Default propagation mask used by C# - we want to propagate deadline
/// and cancellation token by our own means, everything else will be propagated
/// by C core automatically (according to <c>DefaultCoreMask</c>).
/// </summary>
internal const ContextPropagationFlags DefaultMask = DefaultCoreMask
& ~ContextPropagationFlags.Deadline & ~ContextPropagationFlags.Cancellation;
readonly CallSafeHandle parentCall;
readonly DateTime deadline;
readonly CancellationToken cancellationToken;
readonly ContextPropagationOptions options;
internal ContextPropagationTokenImpl(CallSafeHandle parentCall, DateTime deadline, CancellationToken cancellationToken, ContextPropagationOptions options)
{
this.parentCall = GrpcPreconditions.CheckNotNull(parentCall);
this.deadline = deadline;
this.cancellationToken = cancellationToken;
this.options = options ?? ContextPropagationOptions.Default;
}
/// <summary>
/// Gets the native handle of the parent call.
/// </summary>
internal CallSafeHandle ParentCall
{
get
{
return this.parentCall;
}
}
/// <summary>
/// Gets the parent call's deadline.
/// </summary>
internal DateTime ParentDeadline
{
get
{
return this.deadline;
}
}
/// <summary>
/// Gets the parent call's cancellation token.
/// </summary>
internal CancellationToken ParentCancellationToken
{
get
{
return this.cancellationToken;
}
}
/// <summary>
/// Get the context propagation options.
/// </summary>
internal ContextPropagationOptions Options
{
get
{
return this.options;
}
}
}
internal static class ContextPropagationTokenExtensions
{
/// <summary>
/// Converts given <c>ContextPropagationToken</c> to <c>ContextPropagationTokenImpl</c>
/// if possible or returns null.
/// Being able to convert means that the context propagation token is recognized as
/// "ours" (was created by this implementation).
/// </summary>
public static ContextPropagationTokenImpl AsImplOrNull(this ContextPropagationToken instanceOrNull)
{
return instanceOrNull as ContextPropagationTokenImpl;
}
}
}

@ -63,7 +63,7 @@ namespace Grpc.Core
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions options)
{
return new ContextPropagationToken(callHandle, deadline, cancellationToken, options);
return new ContextPropagationTokenImpl(callHandle, deadline, cancellationToken, options);
}
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)

Loading…
Cancel
Save