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(); var writeOptions = new WriteOptions();
Assert.AreSame(writeOptions, options.WithWriteOptions(writeOptions).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); CancellationToken.None, ContextPropagationOptions.Default);
Assert.AreSame(propagationToken, options.WithPropagationToken(propagationToken).PropagationToken); Assert.AreSame(propagationToken, options.WithPropagationToken(propagationToken).PropagationToken);
@ -72,13 +72,13 @@ namespace Grpc.Core.Tests
Assert.AreEqual(DateTime.MaxValue, new CallOptions().Normalize().Deadline.Value); Assert.AreEqual(DateTime.MaxValue, new CallOptions().Normalize().Deadline.Value);
var deadline = DateTime.UtcNow; 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)); new ContextPropagationOptions(propagateDeadline: true, propagateCancellation: false));
Assert.AreEqual(deadline, new CallOptions(propagationToken: propagationToken1).Normalize().Deadline.Value); Assert.AreEqual(deadline, new CallOptions(propagationToken: propagationToken1).Normalize().Deadline.Value);
Assert.Throws(typeof(ArgumentException), () => new CallOptions(deadline: deadline, propagationToken: propagationToken1).Normalize()); Assert.Throws(typeof(ArgumentException), () => new CallOptions(deadline: deadline, propagationToken: propagationToken1).Normalize());
var token = new CancellationTokenSource().Token; 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)); new ContextPropagationOptions(propagateDeadline: false, propagateCancellation: true));
Assert.AreEqual(token, new CallOptions(propagationToken: propagationToken2).Normalize().CancellationToken); Assert.AreEqual(token, new CallOptions(propagationToken: propagationToken2).Normalize().CancellationToken);
Assert.Throws(typeof(ArgumentException), () => new CallOptions(cancellationToken: token, propagationToken: propagationToken2).Normalize()); 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) => helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
{ {
var propagationToken = context.CreatePropagationToken(); var propagationToken = context.CreatePropagationToken();
Assert.IsNotNull(propagationToken.ParentCall); Assert.IsNotNull(propagationToken.AsImplOrNull().ParentCall);
var callOptions = new CallOptions(propagationToken: propagationToken); var callOptions = new CallOptions(propagationToken: propagationToken);
try try
@ -154,5 +154,28 @@ namespace Grpc.Core.Tests
await call.RequestStream.CompleteAsync(); await call.RequestStream.CompleteAsync();
Assert.AreEqual("PASS", await call); 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() internal CallOptions Normalize()
{ {
var newOptions = this; 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, GrpcPreconditions.CheckArgument(!newOptions.deadline.HasValue,
"Cannot propagate deadline from parent call. The deadline has already been set explicitly."); "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, GrpcPreconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled,
"Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value."); "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 #endregion
using System;
using System.Threading;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core namespace Grpc.Core
{ {
/// <summary> /// <summary>
@ -29,127 +23,13 @@ namespace Grpc.Core
/// In situations when a backend is making calls to another backend, /// In situations when a backend is making calls to another backend,
/// it makes sense to propagate properties like deadline and cancellation /// it makes sense to propagate properties like deadline and cancellation
/// token of the server call to the child call. /// token of the server call to the child call.
/// The gRPC native layer provides some other contexts (like tracing context) that /// Underlying gRPC implementation may provide other "opaque" contexts (like tracing context) that
/// are not accessible to explicitly C# layer, but this token still allows propagating them. /// are not explicitly accesible via the public C# API, but this token still allows propagating them.
/// </summary>
public 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> /// </summary>
internal const ContextPropagationFlags DefaultMask = DefaultCoreMask public abstract class ContextPropagationToken
& ~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)
{
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 internal ContextPropagationToken()
{ {
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. 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; var credentials = details.Options.Credentials;
using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null) using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null)
{ {
var result = details.Channel.Handle.CreateCall( 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); details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value), nativeCredentials);
return result; 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) 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) protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)

Loading…
Cancel
Save