英文:
What is the lifetime/scope of ClientInterceptorContext in .NET?
问题
我有一个在.NET 4.6.2上使用Grpc.Core
库的.NET gRPC客户端。
我已将gRPC通道注册为单例以便重用,遵循此建议。
以下是我创建通道实例的方式:
public static InvokerAndChannel<T> CreateGrpcCallInvokerAndChannel<T>(
string endpoint, ChannelCredentials credentials, IServiceProvider serviceProvider)
where T : class
{
var licenseCode = serviceProvider.GetRequiredService<ILicenseCodeResolver>().GetLicenseCode();
var channel = new Channel(endpoint, credentials, new ChannelOption(ChannelOptions.MaxSendMessageLength, "1mb").Yield());
var callInvoker = channel
.Intercept(
new ClientTracingInterceptor(
new ClientTracingInterceptorOptions { RecordMessageEvents = false }))
.Intercept(new StaticMetadataInterceptor(licenseCode));
return new InvokerAndChannel<T>(callInvoker, channel);
}
正如你所见,管道中包括StaticMetadataInterceptor
,它将许可证头添加到调用中。实际上,它也是一个单例。
我对同一gRPC服务的各种方法进行了多个并行调用。我发现StaticMetadataInterceptor
接收到相同的ClientInterceptorContext
。至少context.Options.Headers
不断增长,所以在几次调用后它包含了相同的头几次。为什么?
以下是拦截器的代码:
internal sealed class StaticMetadataInterceptor : Interceptor
{
private readonly string _licenseCode;
public StaticMetadataInterceptor(string licenseCode)
{
_licenseCode = licenseCode;
}
private void ExtendMetadata<TRequest, TResponse>(ref ClientInterceptorContext<TRequest, TResponse> context)
where TRequest : class
where TResponse : class
{
var metadata = context.Options.Headers;
if (metadata == null)
{
metadata = new Metadata();
context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, context.Options.WithHeaders(metadata));
}
metadata.Add(GrpcStandardMetadataKeys.License, _licenseCode);
}
public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
}
英文:
I have a .NET gRPC client on .net 4.6.2 using Grpc.Core
library.
I've registered gRPC channel as singleton to reuse it following [this recommendation].(https://learn.microsoft.com/en-us/aspnet/core/grpc/performance?view=aspnetcore-7.0#reuse-grpc-channels)
There's how I create an instance of the channel:
public static InvokerAndChannel<T> CreateGrpcCallInvokerAndChannel<T>(
string endpoint, ChannelCredentials credentials, IServiceProvider serviceProvider)
where T : class
{
var licenseCode = serviceProvider.GetRequiredService<ILicenseCodeResolver>().GetLicenseCode();
var channel = new Channel(endpoint, credentials, new ChannelOption(ChannelOptions.MaxSendMessageLength, "1mb").Yield());
var callInvoker = channel
.Intercept(
new ClientTracingInterceptor(
new ClientTracingInterceptorOptions { RecordMessageEvents = false }))
.Intercept(new StaticMetadataInterceptor(licenseCode));
return new InvokerAndChannel<T>(callInvoker, channel);
}
As you can see the pipeline includes StaticMetadataInterceptor
which adds license header to the call. Effectively it's also a singleton.
I have multiple parallel calls to various methods of the same gRPC service. And I see that StaticMetadataInterceptor
receives same ClientInterceptorContext
. At least context.Options.Headers
keeps growing, so after several calls it contains the same header several times. Why?
Here's the code for the interceptor:
internal sealed class StaticMetadataInterceptor : Interceptor
{
private readonly string _licenseCode;
public StaticMetadataInterceptor(string licenseCode)
{
_licenseCode = licenseCode;
}
private void ExtendMetadata<TRequest, TResponse>(ref ClientInterceptorContext<TRequest, TResponse> context)
where TRequest : class
where TResponse : class
{
var metadata = context.Options.Headers;
if (metadata == null)
{
metadata = new Metadata();
context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context. Host, context.Options.WithHeaders(metadata));
}
metadata.Add(GrpcStandardMetadataKeys.License, _licenseCode);
}
public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(request, context);
}
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)
{
ExtendMetadata(ref context);
return continuation(context);
}
}
答案1
得分: 1
我找到了原因,为什么头部集合不断增加。
生成的Grpc客户端有一个可选参数,类型为Metadata
。我假设这个集合中的项目将被复制,如果拦截器向出站请求添加标头,初始元数据不会受到影响。这个假设是错误的。
示例:
var metadata = new Metadata();
client.CallFoo(request, metadata);
如果拦截器向请求添加标头,metadata
将会被更改。
英文:
I found the reason, why headers collection kept growing.
Generated Grpc client has an optional parameter of Metada
type. My assumption was that items from this collection will be copied, and if interceptor adds headers to the outgoing request, initial metadata won't be affected. That assumption was wrong.
Example:
var metadata = new Metadata();
client.CallFoo(request, metadata);
If interceptor adds headers to request, metadata
will be changed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论