Error de conexión cerrada entre HttpClient y el servicio web ASP.NET Core 2.0

Tengo un servicio web ASP.NET Core 2.0 que se ejecuta en IIS. Uno de los métodos del controlador se parece más o menos a esto:

[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");
}

Realiza algunas actualizaciones de la base de datos, consulta registros de una tabla, serializa datos y los envía como respuesta. Los datos se envían en formato binario. Estoy usandoMessagePack-CSharp como un serializador.

Luego tengo una aplicación cliente que se comunica con este servicio web. Es la biblioteca .NET Standard 2.0, a la que se hace referencia desde la aplicación de consola .NET 4.6.1. yo sueloHttpClient para solicitar yHttpResponseMessage.Content.ReadAsByteArrayAsync() para leer la respuesta (código exacto ver abajo).

Quería hacer algunas pruebas. Mi mesa tiene cca. 80 columnas y contiene cca. 140000 registros. Se supone que todos ellos deben enviarse al cliente. Obtener datos de db lleva unos segundos, luego todo se serializa y es el resultado de cca. Se envían 34 MB al cliente.

Tengo 10 clientes Cuando llaman al servicio web en serie, todo funciona. Cuando hago hincapié en el servicio web y despido a los clientes en paralelo, casi siempre recibo un error en algunos de ellos (generalmente uno o dos fallan, a veces incluso 4-5).

La excepción sigue y se plantea desdeReadAsByteArrayAsync llamada:

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)
...

Encontré varios hilos SO relacionados con dicha excepción (p. Ej.aquí), por lo que inicialmente pensé que este es un problema relacionado con el cliente. Respuestas sugeridas:

cambiar a HTTP 1.0ajusteConnection: close en lugar deConnection: keep-aliveviceversa punto arriba

Nada me funcionó. Creo que leí en alguna parte que había algún error en HttpClient (no puedo encontrar la fuente ahora). Traté de usar el más nuevoSystem.Net.Http paquete de Nuget. El mismo problema. Creé la aplicación de consola .NET Core y uso la versión Core deHttpClient. El mismo problema. solíaHttpWebRequest en lugar deHttpClient. El mismo problema subyacente.

Estaba ejecutando el servicio web y los clientes en la misma máquina virtual. Solo para descartar algunos problemas locales, ejecuto clientes simultáneamente desde otras computadoras. El mismo problema.

Así que terminé con el siguiente código simplificado (solo una aplicación con 10 hilos):

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;
        }
    }
}

Tenía curiosidad sobre el tráfico real, así que configuréViolinista. Cuando se produce el error, Fiddler muestra que la respuesta está dañada y solo se ha enviado una parte de la cantidad de datos supuesta (6 MB, 20 MB, ... en lugar de 34 MB). Parece que se interrumpe al azar. Jugué un tiempo con Wireshark y vi que el paquete RST / ACK se envía desde el servidor, pero no soy lo suficientemente bueno para analizar una comunicación de tan bajo nivel.

Entonces, me centré en el lado del servidor. Por supuesto, verifiqué dos veces si hay alguna excepción en el método del controlador. Todo funciona bien Configuré el nivel de registro para rastrear y encontré lo siguiente en el registro:

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

No encontré nada interesante y ASP.NET Core específico relacionado con este error. De acuerdo aesta documentación, IIS tiene una opción para especificar la tasa de rendimiento mínima, cuando envía una respuesta al cliente, con la siguiente configuración:

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

Lo uso en miWeb.config, pero no tiene ningún efecto (¿se aplica a las aplicaciones ASP.NET Core o es solo una configuración de marco completo?).

Intenté volverFileStreamResult en lugar deFileContentResult, pero de nuevo, no ayudó.

De manera similar al cliente, traté de encontrar un código reproducible mínimo para el lado del servidor también. Método acaba de tenerThread.Sleep(8000) (en lugar de llamar a db), luego generó una matriz de bytes aleatoria de 50Mb y la devolvió. Esto funcionó sin ningún problema, así que supongo que seguiré investigando en esa dirección. Soy consciente de que db podría tener un cuello de botella aquí, pero no estoy seguro de cómo podría causar esto (sin excepción de tiempo de espera, sin punto muerto, ...).

¿Algún consejo? Al menos me gustaría saber si es un servidor o un problema realmente relacionado con el cliente.

Respuestas a la pregunta(1)

Su respuesta a la pregunta