runtime: Http server performance regression in net6 preview 7

Description

I have a small benchmark program that runs fast with netcore3.1, net5, net6 preview6 and prior (take 1~5 seconds), but the same program runs super slowly in net6 preview 7 (several minutes ~ infinite)

Configuration

Regression?

I can confirm it works fine on netcore3.1, net5, net6 preview 6 and prior

Data

It takes 1~5 seconds normally but several minutes ~ infinite with net6 preview 7

Analysis

N/A

Code

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

static class Program
{
    private const string MimeType = "application/json";
    private static readonly HttpClient s_client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) };

    public static async Task Main(string[] args)
    {
        int n;
        if (args.Length < 1 || !int.TryParse(args[0], out n))
        {
            n = 2000;
        }

        var port = 30000 + new Random().Next(10000);
        var server = CreateWebHostBuilder(port).Build();
        using var cts = new CancellationTokenSource();
        _ = Task.Factory.StartNew(() => server.Start(), TaskCreationOptions.LongRunning);
        var sum = 0;
        var api = $"http://localhost:{port}/";
        var tasks = new List<Task<int>>(n);
        for (var i = 1; i <= n; i++)
        {
            tasks.Add(SendAsync(api, i));
        }
        foreach (var task in tasks)
        {
            sum += await task.ConfigureAwait(false);
        }
        Console.WriteLine(sum);
        System.Environment.Exit(0);
    }

    private static async Task<int> SendAsync(string api, int value)
    {
        var payload = JsonConvert.SerializeObject(new Payload { Value = value });
        while (true)
        {
            try
            {
                var content = new StringContent(payload, Encoding.UTF8, MimeType);
                var response = await s_client.PostAsync(api, content).ConfigureAwait(false);
                return int.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
            }
            catch { }
        }
    }

    private static IWebHostBuilder CreateWebHostBuilder(int port)
    {
        return WebHost.CreateDefaultBuilder()
            .SuppressStatusMessages(true)
            .ConfigureLogging((context, logging) =>
            {
                logging.ClearProviders();
            })
            .UseKestrel(options =>
            {
                options.Limits.MaxRequestBodySize = null;
                options.ListenLocalhost(port);
            })
            .UseStartup<Startup>();
    }
}

public sealed class MyController : Controller
{
    [Route("/")]
    public async Task<int> PostAsync()
    {
        using var sr = new StreamReader(Request.Body);
        var bodyText = await sr.ReadToEndAsync().ConfigureAwait(false);
        var payload = JsonConvert.DeserializeObject<Payload>(bodyText);
        return payload.Value;
    }
}

public class Payload
{
    [JsonProperty("value")]
    public int Value { get; set; }
}

public sealed class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore().AddApplicationPart(Assembly.GetExecutingAssembly());
    }

    public void Configure(
        IApplicationBuilder app,
        IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 25 (19 by maintainers)

Most upvoted comments

Putting it into 6.0 milestone where it was addressed.

@Tratcher see the PR from @stephentoub above.

We did a bunch of refactoring around connection creation and pooling, and introduced a dumb bug where connections were getting created synchronously even when using async APIs.

@ManickaP, can you try reverting https://github.com/dotnet/runtime/pull/56966 and see if the problem comes back? Based on the dump you shared (which shows a lot of threads stuck doing synchronous connects) plus your comment about it not reproducing when client and server were in different processes, I suspect that PR fixed a large portion of the issue.