Closed
Description
I'm using a .NET 5 (5.0.3) on a Windows 10 20H2 19042.844.
Whatever I try, my ASP.NET Core Kestrel server always writes packets to the wire by a maximum size which is 4096 (HTTP 1.1) and 65536 (HTTP 2).
The issue is the overall "download"" performance is bad (for a set of machines + network that would support bigger chunk size), especially with HTTP 1.1. Sometimes the packets size is only a few bytes.
Here is a reproducing code:
class Program
{
static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// uncomment for HTTP 2
//serverOptions.ConfigureEndpointDefaults(listenOptions =>
//{
// listenOptions.Protocols = HttpProtocols.Http2;
//});
webBuilder.UseStartup<Startup>();
});
}
class Client
{
public async Task Download()
{
var client = new HttpClient();
var url = "http://localhost:5000/Test/Download";
var req = new HttpRequestMessage(HttpMethod.Get, url)
{
// uncomment for HTTP 2
//Version = HttpVersion.Version20,
//VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
};
var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var completed = 0L;
using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var buffer = new byte[0x100000];
do
{
var read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false);
if (read == 0)
break;
// read here is always 4096 or 65536!
completed += read;
Console.WriteLine("Read: " + read);
}
while (true);
}
Console.WriteLine("Completed: " + completed);
}
}
class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// run a client in another thread
Task.Run(() => new Client().Download());
}
}
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpGet]
[Route("download")]
public IActionResult Download()
{
// create some temp file
var path = Path.Combine(Path.GetTempPath(), "MyTempFile.bin");
if (!System.IO.File.Exists(path))
{
using (var file = new FileStream(path, FileMode.Create))
{
file.SetLength(0x400000); // 4MB
}
}
// stream it out
var stream = System.IO.File.OpenRead(path);
return new FileStreamResult(stream, "application/octet-stream");
}
}
This is my launchSettings.json:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54611/",
"sslPort": 44346
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"KTest": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
Things I've tried:
- configure Kestrel using all KestrelServerOptions.Limits to the max
- I've also written a custom FileStreamResultExecutor (because the default one seems unconfigurable and uses a 65536-size buffer) with a buffer size > 65536 (obviously only for HTTP/2 in my test), it's used, but it doesn't change anything to the 65536 limit.
- I've tested other web servers just to make sure, and they don't do that
I think this comes from the internal ConcurrentPipeWriter but I'm unsure why.
Is this expected? Or is this a configuration problem? Is it an OS problem? How can I get bigger chunks?