From 57b814b05dd9be8063a600855be0b83a770c5220 Mon Sep 17 00:00:00 2001 From: James Newton-King <james@newtonking.com> Date: Sat, 2 Jul 2022 07:32:39 +0800 Subject: [PATCH] Change QUIC transport to use multiple calls to QuicStream.WriteAsync (#42464) --- src/Servers/Kestrel/Kestrel.slnf | 22 ++++++++++++++++++- .../src/Internal/QuicStreamContext.cs | 21 +++++++++++++++++- .../test/QuicStreamContextTests.cs | 9 +++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/Kestrel.slnf b/src/Servers/Kestrel/Kestrel.slnf index 8a60f5f91b9..db0f9a4af54 100644 --- a/src/Servers/Kestrel/Kestrel.slnf +++ b/src/Servers/Kestrel/Kestrel.slnf @@ -2,19 +2,38 @@ "solution": { "path": "..\\..\\..\\AspNetCore.sln", "projects": [ + "src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", + "src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj", + "src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj", + "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj", "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", + "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", + "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", + "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", + "src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj", + "src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj", "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", + "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", + "src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj", + "src\\Middleware\\HostFiltering\\src\\Microsoft.AspNetCore.HostFiltering.csproj", + "src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj", "src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj", + "src\\Security\\Authentication\\Core\\src\\Microsoft.AspNetCore.Authentication.csproj", + "src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj", + "src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj", "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", + "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj", + "src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj", + "src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj", "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "src\\Servers\\Kestrel\\Core\\test\\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj", "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", @@ -37,7 +56,8 @@ "src\\Servers\\Kestrel\\test\\Sockets.BindTests\\Sockets.BindTests.csproj", "src\\Servers\\Kestrel\\test\\Sockets.FunctionalTests\\Sockets.FunctionalTests.csproj", "src\\Servers\\Kestrel\\tools\\CodeGenerator\\CodeGenerator.csproj", - "src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj" + "src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj", + "src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj" ] } } \ No newline at end of file diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index d7a47b65d53..ec2b4107522 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -378,7 +378,26 @@ internal partial class QuicStreamContext : TransportConnection, IPooledStream, I var isCompleted = result.IsCompleted; if (!buffer.IsEmpty) { - await _stream.WriteAsync(buffer, endStream: isCompleted); + if (buffer.IsSingleSegment) + { + // Fast path when the buffer is a single segment. + await _stream.WriteAsync(buffer.First, endStream: isCompleted); + } + else + { + // When then buffer has multiple segments then write them in a loop. + // We're not using a standard foreach here because we want to detect + // the final write and pass end stream flag with that write. + var enumerator = buffer.GetEnumerator(); + var isLastSegment = !enumerator.MoveNext(); + + while (!isLastSegment) + { + var currentSegment = enumerator.Current; + isLastSegment = !enumerator.MoveNext(); + await _stream.WriteAsync(currentSegment, endStream: isLastSegment && isCompleted); + } + } } output.AdvanceTo(end); diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicStreamContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicStreamContextTests.cs index 1cfe24d2a75..b024e588845 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicStreamContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicStreamContextTests.cs @@ -112,6 +112,8 @@ public class QuicStreamContextTests : TestApplicationErrorLoggerLoggedTest var clientStream = await clientConnection.OpenBidirectionalStreamAsync(); await clientStream.WriteAsync(TestData).DefaultTimeout(); + var readTask = clientStream.ReadUntilEndAsync(); + var serverStream = await serverConnection.AcceptAsync().DefaultTimeout(); var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout(); serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End); @@ -123,6 +125,8 @@ public class QuicStreamContextTests : TestApplicationErrorLoggerLoggedTest await serverStream.Transport.Input.CompleteAsync(); await serverStream.Transport.Output.CompleteAsync(); + await readTask.DefaultTimeout(); + var quicStreamContext = Assert.IsType<QuicStreamContext>(serverStream); // Server starts disposing @@ -178,6 +182,9 @@ public class QuicStreamContextTests : TestApplicationErrorLoggerLoggedTest readResult = await serverStream.Transport.Input.ReadAsync().DefaultTimeout(); Assert.True(readResult.IsCompleted); + Logger.LogInformation("Client starting to read."); + var readingTask = clientStream.ReadUntilEndAsync(); + Logger.LogInformation("Server sending data."); await serverStream.Transport.Output.WriteAsync(testData).DefaultTimeout(); @@ -186,7 +193,7 @@ public class QuicStreamContextTests : TestApplicationErrorLoggerLoggedTest await serverStream.Transport.Output.CompleteAsync().DefaultTimeout(); Logger.LogInformation("Client reading until end of stream."); - var data = await clientStream.ReadUntilEndAsync().DefaultTimeout(); + var data = await readingTask.DefaultTimeout(); Assert.Equal(testData.Length, data.Length); Assert.Equal(testData, data); -- GitLab