Polly 回退策略导致 "ObjectDisposedException: Cannot access a closed Stream" 第二次回退在 ASP.NET Core 3.1 上激活

Polly Fallback policy causes "ObjectDisposedException: Cannot access a closed Stream" the 2nd time fallback is activated on ASP.NET Core 3.1

我正在编写一个 REST API,它使用 Polly 弹性策略调用另一个 REST API。 我让回退策略第一次运行良好,但第二次触发时抛出错误:

System.IO.StreamHelpers.ValidateCopyToArgs(Stream source, Stream destination, int bufferSize)
System.IO.MemoryStream.CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
System.IO.Stream.CopyToAsync(Stream destination)
ProxyBase.HttpResponseMessageResult.ExecuteResultAsync(ActionContext context) in HttpResponseMessageResult.cs
+                await stream.CopyToAsync(context.HttpContext.Response.Body);
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)

HttpResponseMessageResult class 是一个自定义转换器,从客户端在回退处理程序中返回的 HttpResponseMessage 到控制器方法期望的 IActionResult。下面是抛出异常的方法代码:

        context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;

        using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
        {
            await stream.CopyToAsync(context.HttpContext.Response.Body);
            await context.HttpContext.Response.Body.FlushAsync();
        }

这是我的政策:

                var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
                .OrResult(r => r.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
                .FallbackAsync(
                    GetFailoverResponse(fallbackUrl),
                    (ct,cx) => {
                            HttpClient client = new HttpClient();
                              return client.GetAsync(fallbackUrl + ct.Result.RequestMessage.RequestUri.AbsolutePath);
                        });
                clientBuilder.AddPolicyHandler(fallbackPolicy);

我第二次检查返回的 HttpResponseMessage 是新鲜的 - 它是独一无二的,我可以看到当我添加一个随机参数时 queryString 是不同的。

我还尝试用回退调用直接替换实际失败的调用(即让 Polly 脱离等式)- 这样它工作正常,所以 Polly 出于某种原因用关闭的流传递 HttpResponseMessage。

问题似乎与(滥用)使用 FallbackAsync 方法的第二个参数有关:它意味着 运行 在 return 回退结果之前 运行 但不是为了生成结果本身,这就是我在上面代码的损坏版本中所做的。

该方法有各种重载,但它们主要支持两个参数并且在确切的数据类型上有所不同:fallbackResult 和 onFallbackAsync。在损坏的示例中,我 return 将静态 fallbackResult 作为备份,即使我的 onFallbackAsync 委托 returns 任务。我的(错误的)理解是,这将是将由策略 return 编辑的 HTTP 响应。显然,在引擎盖下它实际上是 return 静态响应,它在第二次使用时已经读取了它的流。

解决方案是使用第一个参数的另一个重载,它不会 return 静态 HttpResponseMessage 但会将 HTTP 请求上下文传递给可以执行回退 HTTP 请求的 lambda,如下所示:

    var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
    .OrResult(r => r.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
    .FallbackAsync(
        (res, cx, ct) => {
            HttpClient client = new HttpClient();
            return client.GetAsync(fallbackUrl + res.Result.RequestMessage.RequestUri.AbsolutePath);
        },
        (ct, cx) =>
        {
            return Task.CompletedTask;
        });