ClientInterceptorContext在.NET中的生命周期/范围是什么?

huangapple go评论82阅读模式
英文:

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&lt;T&gt; CreateGrpcCallInvokerAndChannel&lt;T&gt;(
    string endpoint, ChannelCredentials credentials, IServiceProvider serviceProvider)
    where T : class
{
    var licenseCode = serviceProvider.GetRequiredService&lt;ILicenseCodeResolver&gt;().GetLicenseCode();

    var channel = new Channel(endpoint, credentials, new ChannelOption(ChannelOptions.MaxSendMessageLength, &quot;1mb&quot;).Yield());
    var callInvoker = channel
        .Intercept(
            new ClientTracingInterceptor(
                new ClientTracingInterceptorOptions { RecordMessageEvents = false }))
        .Intercept(new StaticMetadataInterceptor(licenseCode));

    return new InvokerAndChannel&lt;T&gt;(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&lt;TRequest, TResponse&gt;(ref ClientInterceptorContext&lt;TRequest, TResponse&gt; context)
            where TRequest : class
            where TResponse : class
    {
        var metadata = context.Options.Headers;
        if (metadata == null)
        {
            metadata = new Metadata();
            context = new ClientInterceptorContext&lt;TRequest, TResponse&gt;(context.Method, context. Host, context.Options.WithHeaders(metadata));
        }

        metadata.Add(GrpcStandardMetadataKeys.License, _licenseCode);
    }

    public override TResponse BlockingUnaryCall&lt;TRequest, TResponse&gt;(TRequest request, ClientInterceptorContext&lt;TRequest, TResponse&gt; context, BlockingUnaryCallContinuation&lt;TRequest, TResponse&gt; continuation)
    {
        ExtendMetadata(ref context);

        return continuation(request, context);
    }

    public override AsyncUnaryCall&lt;TResponse&gt; AsyncUnaryCall&lt;TRequest, TResponse&gt;(TRequest request, ClientInterceptorContext&lt;TRequest, TResponse&gt; context, AsyncUnaryCallContinuation&lt;TRequest, TResponse&gt; continuation)
    {
        ExtendMetadata(ref context);

        return continuation(request, context);
    }

    public override AsyncClientStreamingCall&lt;TRequest, TResponse&gt; AsyncClientStreamingCall&lt;TRequest, TResponse&gt;(ClientInterceptorContext&lt;TRequest, TResponse&gt; context, AsyncClientStreamingCallContinuation&lt;TRequest, TResponse&gt; continuation)
    {
        ExtendMetadata(ref context);

        return continuation(context);
    }

    public override AsyncServerStreamingCall&lt;TResponse&gt; AsyncServerStreamingCall&lt;TRequest, TResponse&gt;(TRequest request, ClientInterceptorContext&lt;TRequest, TResponse&gt; context, AsyncServerStreamingCallContinuation&lt;TRequest, TResponse&gt; continuation)
    {
        ExtendMetadata(ref context);

        return continuation(request, context);
    }

    public override AsyncDuplexStreamingCall&lt;TRequest, TResponse&gt; AsyncDuplexStreamingCall&lt;TRequest, TResponse&gt;(ClientInterceptorContext&lt;TRequest, TResponse&gt; context, AsyncDuplexStreamingCallContinuation&lt;TRequest, TResponse&gt; 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.

huangapple
  • 本文由 发表于 2023年8月10日 18:56:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76875065.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定