1 #region Copyright notice and license
2 
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #endregion
18 
19 using System;
20 using System.Diagnostics;
21 using System.Linq;
22 using System.Threading;
23 using System.Threading.Tasks;
24 using Grpc.Core;
25 using Grpc.Core.Internal;
26 using Grpc.Core.Utils;
27 using NUnit.Framework;
28 
29 namespace Grpc.Core.Tests
30 {
31     public class ContextPropagationTest
32     {
33         MockServiceHelper helper;
34         Server server;
35         Channel channel;
36 
37         [SetUp]
Init()38         public void Init()
39         {
40             helper = new MockServiceHelper();
41 
42             server = helper.GetServer();
43             server.Start();
44             channel = helper.GetChannel();
45         }
46 
47         [TearDown]
Cleanup()48         public void Cleanup()
49         {
50             channel.ShutdownAsync().Wait();
51             server.ShutdownAsync().Wait();
52         }
53 
54         [Test]
PropagateCancellation()55         public async Task PropagateCancellation()
56         {
57             var readyToCancelTcs = new TaskCompletionSource<object>();
58             var successTcs = new TaskCompletionSource<string>();
59 
60             helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
61             {
62                 readyToCancelTcs.SetResult(null);  // child call running, ready to parent call
63 
64                 while (!context.CancellationToken.IsCancellationRequested)
65                 {
66                     await Task.Delay(10);
67                 }
68                 successTcs.SetResult("CHILD_CALL_CANCELLED");
69                 return "";
70             });
71 
72             helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
73             {
74                 var propagationToken = context.CreatePropagationToken();
75                 Assert.IsNotNull(propagationToken.AsImplOrNull().ParentCall);
76 
77                 var callOptions = new CallOptions(propagationToken: propagationToken);
78                 try
79                 {
80                     await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
81                 }
82                 catch(RpcException)
83                 {
84                     // Child call will get cancelled, eat the exception.
85                 }
86                 return "";
87             });
88 
89             var cts = new CancellationTokenSource();
90             var parentCall = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
91             await readyToCancelTcs.Task;
92             cts.Cancel();
93             try
94             {
95                 // cannot use Assert.ThrowsAsync because it uses Task.Wait and would deadlock.
96                 await parentCall;
97                 Assert.Fail();
98             }
99             catch (RpcException)
100             {
101             }
102             Assert.AreEqual("CHILD_CALL_CANCELLED", await successTcs.Task);
103         }
104 
105         [Test]
PropagateDeadline()106         public async Task PropagateDeadline()
107         {
108             var deadline = DateTime.UtcNow.AddDays(7);
109             helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
110             {
111                 Assert.IsTrue(context.Deadline < deadline.AddHours(1));
112                 Assert.IsTrue(context.Deadline > deadline.AddHours(-1));
113                 return Task.FromResult("PASS");
114             });
115 
116             helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
117             {
118                 Assert.Throws(typeof(ArgumentException), () =>
119                 {
120                     // Trying to override deadline while propagating deadline from parent call will throw.
121                     Calls.BlockingUnaryCall(helper.CreateUnaryCall(
122                         new CallOptions(deadline: DateTime.UtcNow.AddDays(8),
123                                         propagationToken: context.CreatePropagationToken())), "");
124                 });
125 
126                 var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken());
127                 return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
128             });
129 
130             var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: deadline)));
131             await call.RequestStream.CompleteAsync();
132             Assert.AreEqual("PASS", await call);
133         }
134 
135         [Test]
SuppressDeadlinePropagation()136         public async Task SuppressDeadlinePropagation()
137         {
138             helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
139             {
140                 Assert.AreEqual(DateTime.MaxValue, context.Deadline);
141                 return Task.FromResult("PASS");
142             });
143 
144             helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
145             {
146                 Assert.IsTrue(context.CancellationToken.CanBeCanceled);
147 
148                 var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken(new ContextPropagationOptions(propagateDeadline: false)));
149                 return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
150             });
151 
152             var cts = new CancellationTokenSource();
153             var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddDays(7))));
154             await call.RequestStream.CompleteAsync();
155             Assert.AreEqual("PASS", await call);
156         }
157 
158         [Test]
ForeignPropagationTokenInterpretedAsNull()159         public void ForeignPropagationTokenInterpretedAsNull()
160         {
161             Assert.IsNull(new ForeignContextPropagationToken().AsImplOrNull());
162         }
163 
164         [Test]
ForeignPropagationTokenIsIgnored()165         public async Task ForeignPropagationTokenIsIgnored()
166         {
167             helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
168             {
169                 return Task.FromResult("PASS");
170             });
171 
172             var callOptions = new CallOptions(propagationToken: new ForeignContextPropagationToken());
173             await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
174         }
175 
176         // For testing, represents context propagation token that's not generated by Grpc.Core
177         private class ForeignContextPropagationToken : ContextPropagationToken
178         {
179         }
180     }
181 }
182