Skip to content

Commit 5c8c102

Browse files
authored
support CancellationToken signatures (#96)
* support CancellationToken signatures; fixes #95; increases signuature options by 19 * additional test (run to completion)
1 parent de85595 commit 5c8c102

File tree

9 files changed

+217
-34
lines changed

9 files changed

+217
-34
lines changed

docs/configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,6 @@ var client = channel.CreateGrpcService<IMyAmazingService>();
6969
var options = new CallOptions(...);
7070
// invoke the RPC call
7171
var result = await client.SearchAsync(request, options);
72-
```
72+
```
73+
74+
(note that a `CancellationToken` can be used in place of `CallContext` above to express optional cancellation; this is a more general purpose API, but does not permit access to headers/trailers/etc).

docs/gettingstarted.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ public interface ICalculator
115115
}
116116
```
117117

118+
If you wish to allow optional cancellation in a general purpose way, and do *not* require access to headers/trailers/etc, then you can use `CancellationToken` in
119+
place of `CallContext`:
120+
121+
``` c#
122+
[ServiceContract(Name = "Hyper.Calculator")]
123+
public interface ICalculator
124+
{
125+
ValueTask<MultiplyResult> MultiplyAsync(MultiplyRequest request, CancellationToken cancellationToken = default);
126+
}
127+
```
128+
129+
118130
If you want to use client/server-streaming or duplex communication, then instead of using `T`, `Task<T>` etc, you can use `IAsyncEnumerable<T>` for
119131
either the data parameter or the return type. For example, we could subscribe to a speaking clock via:
120132

docs/releasenotes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes
22

3+
## (unreleased)
4+
5+
- support `CancellationToken` in service signatures in place of `CallContext` (#95)
6+
37
## 1.0.75
48

59
- addition of [`ClientFactory`](https://www.nuget.org/packages/protobuf-net.Grpc.ClientFactory) support

src/protobuf-net.Grpc/Internal/ContractOperation.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using ProtoBuf.Grpc.Configuration;
66
using System.Threading.Tasks;
77
using System.Linq;
8+
using System.Threading;
89

910
namespace ProtoBuf.Grpc.Internal
1011
{
@@ -50,6 +51,7 @@ internal enum TypeCategory
5051
CallOptions,
5152
ServerCallContext,
5253
CallContext,
54+
CancellationToken,
5355
AsyncUnaryCall,
5456
AsyncClientStreamingCall,
5557
AsyncDuplexStreamingCall,
@@ -90,6 +92,13 @@ internal enum TypeCategory
9092
{ (TypeCategory.CallContext,TypeCategory.None, TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CallContext, MethodType.Unary, ResultKind.Task, VoidKind.Request, VOID, RET) },
9193
{ (TypeCategory.CallContext,TypeCategory.None, TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.CallContext, MethodType.Unary, ResultKind.ValueTask, VoidKind.Request, VOID, RET) },
9294

95+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.Void), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Sync, VoidKind.Both,VOID, VOID)},
96+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.Data), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Sync, VoidKind.Request, VOID, RET)},
97+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.UntypedTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Task, VoidKind.Both, VOID, VOID) },
98+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.UntypedValueTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.ValueTask, VoidKind.Both,VOID, VOID) },
99+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Task, VoidKind.Request, VOID, RET) },
100+
{ (TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.ValueTask, VoidKind.Request, VOID, RET) },
101+
93102
// unary with parameter, with or without a return value
94103
{ (TypeCategory.Data, TypeCategory.None,TypeCategory.None, TypeCategory.Void), (ContextKind.NoContext, MethodType.Unary, ResultKind.Sync, VoidKind.Response, 0, VOID)},
95104
{ (TypeCategory.Data, TypeCategory.None,TypeCategory.None, TypeCategory.Data), (ContextKind.NoContext, MethodType.Unary, ResultKind.Sync, VoidKind.None, 0, RET)},
@@ -105,6 +114,13 @@ internal enum TypeCategory
105114
{ (TypeCategory.Data, TypeCategory.CallContext,TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CallContext, MethodType.Unary, ResultKind.Task, VoidKind.None, 0, RET) },
106115
{ (TypeCategory.Data, TypeCategory.CallContext,TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.CallContext, MethodType.Unary, ResultKind.ValueTask, VoidKind.None, 0, RET) },
107116

117+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.Void), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Sync, VoidKind.Response,0, VOID)},
118+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.Data), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Sync, VoidKind.None, 0, RET)},
119+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.UntypedTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Task, VoidKind.Response, 0, VOID) },
120+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.UntypedValueTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.ValueTask, VoidKind.Response,0, VOID) },
121+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.Task, VoidKind.None, 0, RET) },
122+
{ (TypeCategory.Data, TypeCategory.CancellationToken,TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.CancellationToken, MethodType.Unary, ResultKind.ValueTask, VoidKind.None, 0, RET) },
123+
108124
// client streaming
109125
{ (TypeCategory.IAsyncEnumerable, TypeCategory.None, TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.NoContext, MethodType.ClientStreaming, ResultKind.ValueTask, VoidKind.None, 0, RET) },
110126
{ (TypeCategory.IAsyncEnumerable, TypeCategory.None, TypeCategory.None, TypeCategory.UntypedValueTask), (ContextKind.NoContext, MethodType.ClientStreaming, ResultKind.ValueTask, VoidKind.Response, 0, VOID) },
@@ -116,19 +132,27 @@ internal enum TypeCategory
116132
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CallContext, TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CallContext, MethodType.ClientStreaming, ResultKind.Task, VoidKind.None, 0, RET) },
117133
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CallContext, TypeCategory.None, TypeCategory.UntypedTask), (ContextKind.CallContext, MethodType.ClientStreaming, ResultKind.Task, VoidKind.Response, 0, VOID) },
118134

135+
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.TypedValueTask), (ContextKind.CancellationToken, MethodType.ClientStreaming, ResultKind.ValueTask, VoidKind.None, 0, RET) },
136+
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.UntypedValueTask), (ContextKind.CancellationToken, MethodType.ClientStreaming, ResultKind.ValueTask, VoidKind.Response, 0, VOID) },
137+
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.TypedTask), (ContextKind.CancellationToken, MethodType.ClientStreaming, ResultKind.Task, VoidKind.None, 0, RET) },
138+
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.UntypedTask), (ContextKind.CancellationToken, MethodType.ClientStreaming, ResultKind.Task, VoidKind.Response, 0, VOID) },
139+
119140
// server streaming
120141
{ (TypeCategory.None, TypeCategory.None, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.NoContext, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.Request, VOID, RET) },
121142
{ (TypeCategory.CallContext, TypeCategory.None, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CallContext, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.Request, VOID, RET) },
143+
{ (TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CancellationToken, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.Request, VOID, RET) },
122144
{ (TypeCategory.Data, TypeCategory.None, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.NoContext, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
123145
{ (TypeCategory.Data, TypeCategory.CallContext, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CallContext, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
146+
{ (TypeCategory.Data, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CancellationToken, MethodType.ServerStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
124147

125148
// duplex
126149
{ (TypeCategory.IAsyncEnumerable, TypeCategory.None, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.NoContext, MethodType.DuplexStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
127150
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CallContext, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CallContext, MethodType.DuplexStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
151+
{ (TypeCategory.IAsyncEnumerable, TypeCategory.CancellationToken, TypeCategory.None, TypeCategory.IAsyncEnumerable), (ContextKind.CancellationToken, MethodType.DuplexStreaming, ResultKind.AsyncEnumerable, VoidKind.None, 0, RET) },
128152
};
129153
internal static int SignatureCount => s_signaturePatterns.Count;
130154

131-
internal static int GeneralPurposeSignatureCount() => s_signaturePatterns.Values.Count(x => x.Context == ContextKind.CallContext || x.Context == ContextKind.NoContext);
155+
internal static int GeneralPurposeSignatureCount() => s_signaturePatterns.Values.Count(x => x.Context == ContextKind.CallContext || x.Context == ContextKind.NoContext || x.Context == ContextKind.CancellationToken);
132156

133157
static TypeCategory GetCategory(MarshallerCache marshallerCache, Type type, IBindContext? bindContext)
134158
{
@@ -139,6 +163,7 @@ static TypeCategory GetCategory(MarshallerCache marshallerCache, Type type, IBin
139163
if (type == typeof(ServerCallContext)) return TypeCategory.ServerCallContext;
140164
if (type == typeof(CallOptions)) return TypeCategory.CallOptions;
141165
if (type == typeof(CallContext)) return TypeCategory.CallContext;
166+
if (type == typeof(CancellationToken)) return TypeCategory.CancellationToken;
142167

143168
if (type.IsGenericType)
144169
{
@@ -297,6 +322,7 @@ where method.IsGenericMethodDefinition
297322
{
298323
case ContextKind.CallContext:
299324
case ContextKind.NoContext:
325+
case ContextKind.CancellationToken:
300326
return _clientResponseMap.TryGetValue((MethodType, Result, Void & VoidKind.Response), out var helper) ? helper : null;
301327
default:
302328
return null;
@@ -335,6 +361,7 @@ internal enum ContextKind
335361
CallContext, // pb-net shared context kind
336362
CallOptions, // GRPC core client context kind
337363
ServerCallContext, // GRPC core server context kind
364+
CancellationToken, // cancellation (without extra context)
338365
}
339366

340367
internal enum ResultKind

src/protobuf-net.Grpc/Internal/ProxyEmitter.cs

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
using Grpc.Core;
2+
using ProtoBuf.Grpc.Configuration;
23
using System;
34
using System.Collections.Generic;
45
using System.Linq;
56
using System.Linq.Expressions;
67
using System.Reflection;
78
using System.Reflection.Emit;
8-
using ProtoBuf.Grpc.Configuration;
9-
using System.Diagnostics;
109
using System.Runtime.CompilerServices;
10+
using System.Threading;
1111

1212
namespace ProtoBuf.Grpc.Internal
1313
{
@@ -58,14 +58,27 @@ private static void Ldc_I4(ILGenerator il, int value)
5858
// }
5959
//}
6060

61-
//private static void Ldloca(ILGenerator il, LocalBuilder local)
62-
//{
63-
// switch (local.LocalIndex)
64-
// {
65-
// case int i when (i >= 0 & i <= 255): il.Emit(OpCodes.Ldloca_S, (byte)i); break;
66-
// default: il.Emit(OpCodes.Ldloca, local); break;
67-
// }
68-
//}
61+
private static void Ldloca(ILGenerator il, LocalBuilder local)
62+
{
63+
switch (local.LocalIndex)
64+
{
65+
case int i when (i >= 0 & i <= 255): il.Emit(OpCodes.Ldloca_S, (byte)i); break;
66+
default: il.Emit(OpCodes.Ldloca, local); break;
67+
}
68+
}
69+
70+
private static void Stloc(ILGenerator il, LocalBuilder local)
71+
{
72+
switch (local.LocalIndex)
73+
{
74+
case 0: il.Emit(OpCodes.Stloc_0); break;
75+
case 1: il.Emit(OpCodes.Stloc_1); break;
76+
case 2: il.Emit(OpCodes.Stloc_2); break;
77+
case 3: il.Emit(OpCodes.Stloc_3); break;
78+
case int i when (i >= 4 & i <= 255): il.Emit(OpCodes.Stloc_S, (byte)i); break;
79+
default: il.Emit(OpCodes.Stloc, local); break;
80+
}
81+
}
6982

7083
private static void Ldarga(ILGenerator il, ushort index)
7184
{
@@ -78,18 +91,18 @@ private static void Ldarga(ILGenerator il, ushort index)
7891
il.Emit(OpCodes.Ldarga, index);
7992
}
8093
}
81-
//private static void Ldarg(ILGenerator il, ushort index)
82-
//{
83-
// switch (index)
84-
// {
85-
// case 0: il.Emit(OpCodes.Ldarg_0); break;
86-
// case 1: il.Emit(OpCodes.Ldarg_1); break;
87-
// case 2: il.Emit(OpCodes.Ldarg_2); break;
88-
// case 3: il.Emit(OpCodes.Ldarg_3); break;
89-
// case ushort x when x <= 255: il.Emit(OpCodes.Ldarg_S, (byte)x); break;
90-
// default: il.Emit(OpCodes.Ldarg, index); break;
91-
// }
92-
//}
94+
private static void Ldarg(ILGenerator il, ushort index)
95+
{
96+
switch (index)
97+
{
98+
case 0: il.Emit(OpCodes.Ldarg_0); break;
99+
case 1: il.Emit(OpCodes.Ldarg_1); break;
100+
case 2: il.Emit(OpCodes.Ldarg_2); break;
101+
case 3: il.Emit(OpCodes.Ldarg_3); break;
102+
case ushort x when x <= 255: il.Emit(OpCodes.Ldarg_S, (byte)x); break;
103+
default: il.Emit(OpCodes.Ldarg, index); break;
104+
}
105+
}
93106

94107
static int _typeIndex;
95108
private static readonly MethodInfo s_marshallerCacheGenericMethodDef
@@ -219,6 +232,7 @@ FieldBuilder Marshaller(Type forType)
219232
break;
220233
case ContextKind.NoContext:
221234
case ContextKind.CallContext:
235+
case ContextKind.CancellationToken:
222236
// typically looks something like (where this is an extension method on Reshape):
223237
// => context.{ReshapeMethod}(CallInvoker, {method}, request, [host: null]);
224238
var method = op.TryGetClientHelper();
@@ -229,13 +243,24 @@ FieldBuilder Marshaller(Type forType)
229243
}
230244
else
231245
{
232-
if (op.Context == ContextKind.CallContext)
246+
switch (op.Context)
233247
{
234-
Ldarga(il, op.VoidRequest ? (ushort)1 : (ushort)2);
235-
}
236-
else
237-
{
238-
il.Emit(OpCodes.Ldsflda, s_CallContext_Default);
248+
case ContextKind.CallContext:
249+
Ldarga(il, op.VoidRequest ? (ushort)1 : (ushort)2);
250+
break;
251+
case ContextKind.CancellationToken:
252+
var callContext = il.DeclareLocal(typeof(CallContext));
253+
Ldarg(il, op.VoidRequest ? (ushort)1 : (ushort)2);
254+
il.EmitCall(OpCodes.Call, s_CallContext_FromCancellationToken, null);
255+
Stloc(il, callContext);
256+
Ldloca(il, callContext);
257+
break;
258+
case ContextKind.NoContext:
259+
il.Emit(OpCodes.Ldsflda, s_CallContext_Default);
260+
break;
261+
default:
262+
// shouldn't get here - we checked above! this is in case of code maintenance errors
263+
throw new NotImplementedException($"Unhandled context kind: {op.Context}");
239264
}
240265
il.Emit(OpCodes.Ldarg_0); // this.
241266
il.EmitCall(OpCodes.Callvirt, callInvoker, null); // get_CallInvoker
@@ -297,12 +322,15 @@ FieldBuilder Marshaller(Type forType)
297322
}
298323
}
299324
}
300-
#pragma warning disable CS0618 // Empty
301325
internal static readonly FieldInfo
302326
s_CallContext_Default = typeof(CallContext).GetField(nameof(CallContext.Default))!,
327+
#pragma warning disable CS0618 // Empty
303328
s_Empty_Instance = typeof(Empty).GetField(nameof(Empty.Instance))!,
304329
s_Empty_InstaneTask= typeof(Empty).GetField(nameof(Empty.InstanceTask))!;
305330
#pragma warning restore CS0618
331+
332+
internal static readonly MethodInfo s_CallContext_FromCancellationToken = typeof(CallContext).GetMethod("op_Implicit", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(CancellationToken) }, null)!;
333+
306334
internal const string FactoryName = "Create";
307335
}
308336
}

0 commit comments

Comments
 (0)