Erro de conexão encerrada entre o HttpClient e o serviço Web ASP.NET Core 2.0

Eu tenho um serviço da Web ASP.NET Core 2.0 em execução no IIS. Um dos métodos do controlador é mais ou menos assim:

[HttpGet()]
public IActionResult Test()
{
    // do some db updates and get data
    var result = DoSomeStuff();
    // serialize data to byte array
    var output = Serialize(result);

    return File(output, "application/octet-stream");
}

Ele faz algumas atualizações do banco de dados, consulta registros de uma tabela, serializa dados e os envia como resposta. Os dados são enviados em formato binário. estou a usarMessagePack-CSharp como um serializador.

Então, eu tenho um aplicativo cliente que se comunica com este serviço da web. É a biblioteca do .NET Standard 2.0, que é referenciada no aplicativo de console do .NET 4.6.1. eu usoHttpClient para solicitar eHttpResponseMessage.Content.ReadAsByteArrayAsync() para ler a resposta (código exato, veja abaixo).

Eu queria fazer alguns testes. Minha mesa tem cca. 80 colunas e contém cca. 140000 registros. Todos eles devem ser enviados ao cliente. Obter dados do banco de dados leva alguns segundos, então tudo é serializado e o resultado do cca. 34 MB são enviados para o cliente.

Eu tenho 10 clientes. Quando eles chamam o serviço da web em série, tudo funciona. Quando enfatizo o serviço web e atendo clientes em paralelo, quase sempre recebo um erro em alguns deles (geralmente uma ou duas falhas, às vezes até 4-5).

A exceção está a seguir e é gerada a partir deReadAsByteArrayAsync ligar:

System.AggregateException: One or more errors occurred. ---> System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
   at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
   --- End of inner exception stack trace ---
   at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
   at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...
---> (Inner Exception #0) System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
   at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
   --- End of inner exception stack trace ---
   at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
   at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...

Encontrei vários threads de SO relacionados a essa exceção (por exemplo,aqui), então inicialmente pensei que este fosse um problema relacionado ao cliente. Respostas sugeridas:

alternando para HTTP 1.0configuraçãoConnection: close ao invés deConnection: keep-alivevice-versa acima

Nada funcionou para mim. Acho que li em algum lugar que houve algum bug no HttpClient (não é possível encontrar a fonte agora). Eu tentei usar o mais novoSystem.Net.Http pacote de Nuget. Mesmo problema. Criei o aplicativo de console .NET Core e use a versão Core doHttpClient. Mesmo problema. eu useiHttpWebRequest ao invés deHttpClient. O mesmo problema subjacente.

Eu estava executando o serviço da web e os clientes na mesma máquina VM. Apenas para descartar alguns problemas locais, eu executo clientes simultaneamente de outros computadores. Mesmo problema.

Então, acabei com o seguinte código simplificado (apenas um aplicativo com 10 threads):

private async void Test_Click(object sender, RoutedEventArgs e)
{
    try
    {
        var tasks = Enumerable.Range(1, 10).Select(async i => await Task.Run(async () => await GetContent(i))).ToList();

        await Task.WhenAll(tasks);

        MessageBox.Show(String.Join(Environment.NewLine, tasks.Select(t => t.Result.ToString())));
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

private async Task<Int32> GetContent(Int32 id)
{
    using (var httpClient = new HttpClient())
    {
        var url = "http://localhost/TestService/api/test";

        using (var responseMessage = await httpClient.GetAsync(url).ConfigureAwait(false))
        {
            // just read everything and return length
            // ReadAsByteArrayAsync throws sometimes an exception
            var content = await responseMessage.Content.ReadAsByteArrayAsync();
            return content.Length;
        }
    }
}

Eu estava curioso sobre o tráfego real, então configureiViolinista. Quando o erro ocorre, o Fiddler mostra que a resposta está realmente corrompida e apenas parte da suposta quantidade de dados foi realmente enviada (6 MB, 20 MB, ... em vez de 34 MB). Parece que é interrompido aleatoriamente. Joguei um pouco com o Wireshark e vi que o pacote RST / ACK é enviado do servidor, mas não sou bom o suficiente com a análise de comunicações de baixo nível.

Então, eu me concentrei no lado do servidor. É claro que verifiquei novamente se há alguma exceção no método do controlador. Tudo funciona bem. Defino o nível do log para rastrear e encontrei o seguinte no log:

info: Microsoft.AspNetCore.Server.Kestrel[28]
      Connection id "0HL89D9NUNEOQ", Request id "0HL89D9NUNEOQ:00000001": the connection was closed becuase the response was not read by the client at the specified minimum data rate.
dbug: Microsoft.AspNetCore.Server.Kestrel[10]
      Connection id "0HL89D9NUNEOQ" disconnecting.
...
info: Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv[14]
      Connection id "0HL89D9NUNEOQ" communication error.
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking.UvException: Error -4081 ECANCELED operation canceled

Não encontrei nada de interessante e específico do ASP.NET Core relacionado a esse erro. De acordo comesta documentação, O IIS tem uma opção para especificar a taxa de taxa de transferência mínima, quando envia uma resposta ao cliente, com a seguinte configuração:

<system.applicationHost>
  <webLimits minBytesPerSecond="0"/>
</system.applicationHost>

Eu uso no meuWeb.config, mas não tem efeito (é aplicado aos aplicativos ASP.NET Core ou é apenas uma configuração de estrutura completa?).

Eu tentei voltarFileStreamResult ao invés deFileContentResult, mas novamente - não ajudou.

Da mesma forma que o cliente, tentei encontrar também um código reproduzível mínimo para o servidor. O método acabou de terThread.Sleep(8000) (em vez de chamada db), gerou uma matriz aleatória de 50 MB de bytes e a retornou. Isso funcionou sem nenhum problema, então acho que continuarei investigando nessa direção. Estou ciente de que o db pode ser um gargalo aqui, mas não tenho certeza de como isso poderia causar isso (sem exceção de tempo limite, sem conflito, ...).

Algum conselho? Eu gostaria de saber pelo menos se é realmente um problema relacionado ao servidor ou ao cliente.

questionAnswers(1)

yourAnswerToTheQuestion