Skip to content

Commit 2deebf0

Browse files
[HTTP/3] Stress hack for msquic dropping connections (#84793)
* Implement the same hack as for functional tests to prevent msquic from dropping connections * Feedback: removed code sharing and used reflaction * Try to fix missing build dependency * Feedback - removed test only function and replaced with shared code + some reflection * fix argument handling in build-local.ps1 * do not use MsQuicLibraryVersion * copy msquic interop utils to the SDK base image * use LinkBase in Functional.Tests.csproj for wildcard include * Use MsQuicLibraryVersion in out internal logging and as a side-effect avoid the property being trimmed * Comment * also copy files to the Linux container --------- Co-authored-by: antonfirsov <[email protected]>
1 parent 375cc6d commit 2deebf0

File tree

12 files changed

+63
-22
lines changed

12 files changed

+63
-22
lines changed

eng/docker/build-docker-sdk.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ if ($buildWindowsContainers)
3636
# 2. Runtime pack (microsoft.netcore.app.runtime.win-x64)
3737
# 3. targetingpacks.targets, so stress test builds can target the live-built runtime instead of the one in the pre-installed SDK
3838
# 4. testhost
39+
# 5. msquic interop sources (needed for HttpStress)
3940
$binArtifacts = "$REPO_ROOT_DIR\artifacts\bin"
4041
$dockerContext = "$REPO_ROOT_DIR\artifacts\docker-context"
4142

@@ -51,6 +52,8 @@ if ($buildWindowsContainers)
5152
-Destination $dockerContext\testhost
5253
Copy-Item -Recurse -Path $REPO_ROOT_DIR\eng\targetingpacks.targets `
5354
-Destination $dockerContext\targetingpacks.targets
55+
Copy-Item -Recurse -Path $REPO_ROOT_DIR\src\libraries\System.Net.Quic\src\System\Net\Quic\Interop `
56+
-Destination $dockerContext\msquic-interop
5457

5558
# In case of non-CI builds, testhost may already contain Microsoft.AspNetCore.App (see build-local.ps1 in HttpStress):
5659
$testHostAspNetCorePath="$dockerContext\testhost\net$dotNetVersion-windows-$configuration-x64/shared/Microsoft.AspNetCore.App"

eng/docker/libraries-sdk.linux.Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ RUN bash ./dotnet-install.sh --channel $_DOTNET_INSTALL_CHANNEL --quality daily
2626
# 2. Runtime pack (microsoft.netcore.app.runtime.linux-x64)
2727
# 3. targetingpacks.targets, so stress test builds can target the live-built runtime instead of the one in the pre-installed SDK
2828
# 4. testhost
29+
# 5. msquic interop sources (needed for HttpStress)
2930

3031
COPY --from=corefxbuild \
3132
/repo/artifacts/bin/microsoft.netcore.app.ref \
@@ -43,6 +44,10 @@ COPY --from=corefxbuild \
4344
/repo/artifacts/bin/testhost \
4445
/live-runtime-artifacts/testhost
4546

47+
COPY --from=corefxbuild \
48+
/repo/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop \
49+
/live-runtime-artifacts/msquic-interop
50+
4651
# Add AspNetCore bits to testhost:
4752
ENV _ASPNETCORE_SOURCE="/usr/share/dotnet/shared/Microsoft.AspNetCore.App/$VERSION*"
4853
ENV _ASPNETCORE_DEST="/live-runtime-artifacts/testhost/net$VERSION-linux-$CONFIGURATION-x64/shared/Microsoft.AspNetCore.App"

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Directory.Build.props

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
<!-- Stress projects have their own global.json, the directory above that also has it is the repository root. -->
77
<RepositoryRoot>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)../, global.json))/</RepositoryRoot>
8-
8+
9+
<MsQuicInteropIncludes Condition="'$(MsQuicInteropIncludes)' == ''">$(RepositoryRoot)src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/*.cs</MsQuicInteropIncludes>
910
<TargetingPacksTargetsLocation Condition="'$(TargetingPacksTargetsLocation)' == ''">$(RepositoryRoot)eng/targetingpacks.targets</TargetingPacksTargetsLocation>
1011
<ProductVersion>8.0.0</ProductVersion>
1112
<NetCoreAppCurrent>net8.0</NetCoreAppCurrent>
@@ -14,4 +15,4 @@
1415
<MicrosoftNetCoreAppRefPackDir Condition="'$(MicrosoftNetCoreAppRefPackDir)' == ''" >$(RepositoryRoot)artifacts/bin/microsoft.netcore.app.ref/</MicrosoftNetCoreAppRefPackDir>
1516
<MicrosoftNetCoreAppRuntimePackDir Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == ''">$(RepositoryRoot)artifacts/bin/microsoft.netcore.app.runtime.$(OutputRID)/$(Configuration)/</MicrosoftNetCoreAppRuntimePackDir>
1617
</PropertyGroup>
17-
</Project>
18+
</Project>

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ WORKDIR /app
2828
COPY . .
2929

3030
RUN dotnet build -c $CONFIGURATION \
31+
-p:MsQuicInteropIncludes="/live-runtime-artifacts/msquic-interop/*.cs" \
3132
-p:TargetingPacksTargetsLocation=/live-runtime-artifacts/targetingpacks.targets \
3233
-p:MicrosoftNetCoreAppRefPackDir=/live-runtime-artifacts/microsoft.netcore.app.ref/ \
3334
-p:MicrosoftNetCoreAppRuntimePackDir=/live-runtime-artifacts/microsoft.netcore.app.runtime.linux-x64/$CONFIGURATION/

src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
55
<Nullable>enable</Nullable>
66
<EnablePreviewFeatures>True</EnablePreviewFeatures>
7+
<NoWarn>CA2252</NoWarn>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
79
</PropertyGroup>
810

911
<ItemGroup>
@@ -15,6 +17,11 @@
1517
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.4" />
1618
</ItemGroup>
1719

20+
<!-- Shared production code -->
21+
<ItemGroup>
22+
<Compile Include="$(MsQuicInteropIncludes)" LinkBase="MsQuicInterop" />
23+
</ItemGroup>
24+
1825
<PropertyGroup>
1926
<!-- These may lead to duplicate generated classes with local (non-docker) Linux builds. -->
2027
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
using System.Threading.Tasks;
1414
using System.Net;
1515
using HttpStress;
16+
using System.Net.Quic;
17+
using Microsoft.Quic;
1618

1719
[assembly:SupportedOSPlatform("windows")]
1820
[assembly:SupportedOSPlatform("linux")]
@@ -26,6 +28,8 @@ public static class Program
2628
{
2729
public enum ExitCode { Success = 0, StressError = 1, CliError = 2 };
2830

31+
public static readonly bool IsQuicSupported = QuicListener.IsSupported && QuicConnection.IsSupported;
32+
2933
public static async Task<int> Main(string[] args)
3034
{
3135
if (!TryParseCli(args, out Configuration? config))
@@ -158,6 +162,9 @@ private static async Task<ExitCode> Run(Configuration config)
158162

159163
string GetAssemblyInfo(Assembly assembly) => $"{assembly.Location}, modified {new FileInfo(assembly.Location).LastWriteTime}";
160164

165+
Type msQuicApiType = typeof(QuicConnection).Assembly.GetType("System.Net.Quic.MsQuicApi");
166+
string msQuicLibraryVersion = (string)msQuicApiType.GetProperty("MsQuicLibraryVersion", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).Invoke(null, Array.Empty<object?>());
167+
161168
Console.WriteLine(" .NET Core: " + GetAssemblyInfo(typeof(object).Assembly));
162169
Console.WriteLine(" ASP.NET Core: " + GetAssemblyInfo(typeof(WebHost).Assembly));
163170
Console.WriteLine(" System.Net.Http: " + GetAssemblyInfo(typeof(System.Net.Http.HttpClient).Assembly));
@@ -169,6 +176,8 @@ private static async Task<ExitCode> Run(Configuration config)
169176
Console.WriteLine(" Concurrency: " + config.ConcurrentRequests);
170177
Console.WriteLine(" Content Length: " + config.MaxContentLength);
171178
Console.WriteLine(" HTTP Version: " + config.HttpVersion);
179+
Console.WriteLine(" QUIC supported: " + (IsQuicSupported ? "yes" : "no"));
180+
Console.WriteLine(" MsQuic Version: " + msQuicLibraryVersion);
172181
Console.WriteLine(" Lifetime: " + (config.ConnectionLifetime.HasValue ? $"{config.ConnectionLifetime.Value.TotalMilliseconds}ms" : "(infinite)"));
173182
Console.WriteLine(" Operations: " + string.Join(", ", usedClientOperations.Select(o => o.name)));
174183
Console.WriteLine(" Random Seed: " + config.RandomSeed);
@@ -177,6 +186,23 @@ private static async Task<ExitCode> Run(Configuration config)
177186
Console.WriteLine("Query Parameters: " + config.MaxParameters);
178187
Console.WriteLine();
179188

189+
if (config.HttpVersion == HttpVersion.Version30 && IsQuicSupported)
190+
{
191+
unsafe
192+
{
193+
// If the system gets overloaded, MsQuic has a tendency to drop incoming connections, see https://github.com/dotnet/runtime/issues/55979.
194+
// So in case we're running H/3 stress test, we're using the same hack as for System.Net.Quic tests, which increases the time limit for pending operations in MsQuic thread pool.
195+
object msQuicApiInstance = msQuicApiType.GetProperty("Api", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).Invoke(null, Array.Empty<object?>());
196+
QUIC_API_TABLE* apiTable = (QUIC_API_TABLE*)(Pointer.Unbox(msQuicApiType.GetProperty("ApiTable").GetGetMethod().Invoke(msQuicApiInstance, Array.Empty<object?>())));
197+
QUIC_SETTINGS settings = default(QUIC_SETTINGS);
198+
settings.IsSet.MaxWorkerQueueDelayUs = 1;
199+
settings.MaxWorkerQueueDelayUs = 2_500_000u; // 2.5s, 10x the default
200+
if (MsQuic.StatusFailed(apiTable->SetParam(null, MsQuic.QUIC_PARAM_GLOBAL_SETTINGS, (uint)sizeof(QUIC_SETTINGS), (byte*)&settings)))
201+
{
202+
Console.WriteLine($"Unable to set MsQuic MaxWorkerQueueDelayUs.");
203+
}
204+
}
205+
}
180206

181207
StressServer? server = null;
182208
if (config.RunMode.HasFlag(RunMode.server))

src/libraries/System.Net.Http/tests/StressTests/HttpStress/build-local.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ if (-not ([string]::IsNullOrEmpty($args[0]))) {
1414

1515
$LibrariesConfiguration = "Release"
1616
if (-not ([string]::IsNullOrEmpty($args[1]))) {
17-
$LibrariesConfiguration = $args[0]
17+
$LibrariesConfiguration = $args[1]
1818
}
1919

2020
$TestHostRoot="$RepoRoot/artifacts/bin/testhost/net$Version-windows-$LibrariesConfiguration-x64"
@@ -53,11 +53,11 @@ if (-not (Test-Path -Path "$TestHostRoot/shared/Microsoft.AspNetCore.App")) {
5353
Write-Host "Building solution."
5454
dotnet build -c $StressConfiguration
5555

56-
$Runscript=".\run-stress-$LibrariesConfiguration-$StressConfiguration.ps1"
56+
$Runscript=".\run-stress-$StressConfiguration-$LibrariesConfiguration.ps1"
5757
if (-not (Test-Path $Runscript)) {
5858
Write-Host "Generating Runscript."
5959
Add-Content -Path $Runscript -Value "& '$TestHostRoot/dotnet' exec --roll-forward Major ./bin/$StressConfiguration/net$Version/HttpStress.dll `$args"
6060
}
6161

6262
Write-Host "To run tests type:"
63-
Write-Host "$Runscript [stress test args]"
63+
Write-Host "$Runscript [stress test args]"

src/libraries/System.Net.Http/tests/StressTests/HttpStress/windows.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ ARG VERSION=8.0
1212
ARG CONFIGURATION=Release
1313

1414
RUN dotnet build -c $env:CONFIGURATION `
15+
-p:MsQuicInteropIncludes="C:/live-runtime-artifacts/msquic-interop/*.cs" `
1516
-p:TargetingPacksTargetsLocation=C:/live-runtime-artifacts/targetingpacks.targets `
1617
-p:MicrosoftNetCoreAppRefPackDir=C:/live-runtime-artifacts/microsoft.netcore.app.ref/ `
1718
-p:MicrosoftNetCoreAppRuntimePackDir=C:/live-runtime-artifacts/microsoft.netcore.app.runtime.win-x64/$env:CONFIGURATION/

src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ static MsQuicApi()
130130
}
131131
string? gitHash = Marshal.PtrToStringUTF8((IntPtr)libGitHash);
132132

133-
MsQuicLibraryVersion = $"{Interop.Libraries.MsQuic} version={version} commit={gitHash}";
133+
MsQuicLibraryVersion = $"{Interop.Libraries.MsQuic} {version} ({gitHash})";
134134

135135
if (version < s_minMsQuicVersion)
136136
{
@@ -143,7 +143,7 @@ static MsQuicApi()
143143

144144
if (NetEventSource.Log.IsEnabled())
145145
{
146-
NetEventSource.Info(null, $"Loaded MsQuic library version '{version}', commit '{gitHash}'.");
146+
NetEventSource.Info(null, $"Loaded MsQuic library '{MsQuicLibraryVersion}'.");
147147
}
148148

149149
// Assume SChannel is being used on windows and query for the actual provider from the library if querying is supported

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ public async Task ConnectWithServerCertificateCallback()
335335
// TODO: the exception may change if we implement https://github.com/dotnet/runtime/issues/73152 to make server close
336336
// connections with CONNECTION_REFUSED in such cases
337337
var authEx = await Assert.ThrowsAsync<AuthenticationException>(() => clientTask);
338-
Assert.Contains("UserCanceled", authEx.Message);
338+
Assert.Contains(TlsAlertMessage.UserCanceled.ToString(), authEx.Message);
339339
Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName);
340340
await Assert.ThrowsAsync<ArgumentException>(async () => await listener.AcceptConnectionAsync());
341341

0 commit comments

Comments
 (0)