diff --git a/.azure/pipelines/blazor-daily-tests.yml b/.azure/pipelines/blazor-daily-tests.yml index c22f30db60dd..df3d335489d2 100644 --- a/.azure/pipelines/blazor-daily-tests.yml +++ b/.azure/pipelines/blazor-daily-tests.yml @@ -7,7 +7,6 @@ # We just need one Windows machine because all it does is trigger SauceLabs. variables: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - group: DotNet-MSRC-Storage - group: AzureDevOps-Artifact-Feeds-Pats - name: SAUCE_CONNECT_DOWNLOAD_ON_INSTALL value: true diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 2b65d7e703b6..b72915d1d416 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -135,7 +135,7 @@ extends: sdl: sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows spotBugs: enabled: false diff --git a/.azure/pipelines/helix-matrix.yml b/.azure/pipelines/helix-matrix.yml index b2c9c1a4cc06..33b1a3c483fa 100644 --- a/.azure/pipelines/helix-matrix.yml +++ b/.azure/pipelines/helix-matrix.yml @@ -12,7 +12,6 @@ schedules: branches: include: - release/6.0 - - release/7.0 - release/8.0 always: false diff --git a/.azure/pipelines/identitymodel-helix-matrix.yml b/.azure/pipelines/identitymodel-helix-matrix.yml new file mode 100644 index 000000000000..55f0f8dd8063 --- /dev/null +++ b/.azure/pipelines/identitymodel-helix-matrix.yml @@ -0,0 +1,101 @@ +# We only want to run IdentityModel matrix on main +pr: none +trigger: none +schedules: +# Cron timezone is UTC. +- cron: "0 */12 * * *" + branches: + include: + - release/8.0 + always: true + +variables: +- name: _UseHelixOpenQueues + value: false +- group: DotNet-HelixApi-Access +- template: /eng/common/templates-official/variables/pool-providers.yml@self + +resources: + repositories: + # Repo: 1ESPipelineTemplates/1ESPipelineTemplates + - repository: 1esPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + sdl: + sourceAnalysisPool: + name: NetCore1ESPool-Svc-Internal + image: 1es-windows-2022 + os: windows + codeql: + compiled: + enabled: false + justificationForDisabling: 'This is a test-only pipeline. The same product code is already scanned in the main pipeline (aspnetcore-ci)' + + stages: + - stage: build + displayName: Build + jobs: + - template: .azure/pipelines/jobs/default-build.yml@self + parameters: + jobName: IdentityModel_helix_matrix_x64 + jobDisplayName: 'Tests: IdentityModel nightlies helix full matrix x64' + agentOs: Windows + timeoutInMinutes: 300 + steps: + - task: NuGetAuthenticate@1 + inputs: + forceReinstallCredentialProvider: true + - task: NuGetCommand@2 + displayName: Install Microsoft.IdentityModel.Logging + inputs: + command: 'custom' + arguments: 'install Microsoft.IdentityModel.Logging + -Source https://pkgs.dev.azure.com/dnceng/internal/_packaging/identitymodel-nightlies/nuget/v3/index.json + -DependencyVersion Highest -OutputDirectory $(Build.StagingDirectory) -PreRelease' + - task: NuGetCommand@2 + displayName: Install Microsoft.IdentityModel.Protocols.OpenIdConnect + inputs: + command: 'custom' + arguments: 'install Microsoft.IdentityModel.Protocols.OpenIdConnect + -Source https://pkgs.dev.azure.com/dnceng/internal/_packaging/identitymodel-nightlies/nuget/v3/index.json + -DependencyVersion Highest -OutputDirectory $(Build.StagingDirectory) -PreRelease' + - task: NuGetCommand@2 + displayName: Install Microsoft.IdentityModel.Protocols.WsFederation + inputs: + command: 'custom' + arguments: 'install Microsoft.IdentityModel.Protocols.WsFederation + -Source https://pkgs.dev.azure.com/dnceng/internal/_packaging/identitymodel-nightlies/nuget/v3/index.json + -DependencyVersion Highest -OutputDirectory $(Build.StagingDirectory) -PreRelease' + - task: NuGetCommand@2 + displayName: System.IdentityModel.Tokens.Jwt + inputs: + command: 'custom' + arguments: 'install System.IdentityModel.Tokens.Jwt + -Source https://pkgs.dev.azure.com/dnceng/internal/_packaging/identitymodel-nightlies/nuget/v3/index.json + -DependencyVersion Highest -OutputDirectory $(Build.StagingDirectory) -PreRelease' + - task: PowerShell@2 + displayName: Add IdentityModel feel to NuGet.config + inputs: + filePath: $(Build.SourcesDirectory)/eng/scripts/SetupIdentitySources.ps1 + arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -IdentityModelPackageSource $(Build.StagingDirectory) + # Build the shared framework + - script: ./eng/build.cmd -ci -nobl -all -pack -arch x64 + /p:CrossgenOutput=false /p:IsIdentityModelTestJob=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log + displayName: Build shared fx + # -noBuildRepoTasks -noBuildNative -noBuild to avoid repeating work done in the previous step. + - script: .\eng\build.cmd -ci -nobl -all -noBuildRepoTasks -noBuildNative -noBuild -test + -projects eng\helix\helix.proj /p:IsHelixJob=true /p:RunTemplateTests=false + /p:CrossgenOutput=false /p:IsIdentityModelTestJob=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log + displayName: Run build.cmd helix target + env: + HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues + SYSTEM_ACCESSTOKEN: $(System.AccessToken) # We need to set this env var to publish helix results to Azure Dev Ops + artifacts: + - name: Helix_logs + path: artifacts/log/ + publishOnError: true \ No newline at end of file diff --git a/.azure/pipelines/signalr-daily-tests.yml b/.azure/pipelines/signalr-daily-tests.yml index 5bedd10fc3f4..ad33363fad91 100644 --- a/.azure/pipelines/signalr-daily-tests.yml +++ b/.azure/pipelines/signalr-daily-tests.yml @@ -6,7 +6,6 @@ variables: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - - group: DotNet-MSRC-Storage - group: AzureDevOps-Artifact-Feeds-Pats - template: /eng/common/templates/variables/pool-providers.yml diff --git a/Directory.Build.props b/Directory.Build.props index 9ae6760c645e..d080a9e0fb8e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -117,6 +117,8 @@ $(NoWarn.Replace('1591', '')) $(NoWarn);0105 + + $(NoWarn);NU5104 $(WarningsNotAsErrors);CS1591 diff --git a/NuGet.config b/NuGet.config index a67fb5e3b1f1..0049dc24f34d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -30,10 +30,10 @@ - + - + diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props index 49210d5502a1..a89151e8a015 100644 --- a/eng/Baseline.Designer.props +++ b/eng/Baseline.Designer.props @@ -2,117 +2,117 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -120,137 +120,137 @@ - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - - - + + + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - - + + @@ -258,7 +258,7 @@ - 8.0.4 + 8.0.5 @@ -267,133 +267,133 @@ - 8.0.4 + 8.0.5 - + - + - + - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - - + + - + - - + + - + - - + + - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 @@ -402,7 +402,7 @@ - 8.0.4 + 8.0.5 @@ -410,71 +410,71 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 - - + + - 8.0.4 + 8.0.5 @@ -490,27 +490,27 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 @@ -519,23 +519,23 @@ - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -544,54 +544,54 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - - + + - - + + - - + + - 8.0.4 + 8.0.5 - - + + - - + + - - + + - - + + @@ -599,83 +599,83 @@ - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - - - - + + + + - 8.0.4 + 8.0.5 @@ -684,64 +684,64 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -763,7 +763,7 @@ - 8.0.4 + 8.0.5 @@ -785,7 +785,7 @@ - 8.0.4 + 8.0.5 @@ -801,23 +801,23 @@ - 8.0.4 + 8.0.5 - + - + - + @@ -825,24 +825,24 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - - - + + + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -852,7 +852,7 @@ - 8.0.4 + 8.0.5 @@ -861,73 +861,73 @@ - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - + - + - + - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -956,11 +956,11 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 @@ -978,18 +978,18 @@ - 8.0.4 + 8.0.5 - 8.0.4 + 8.0.5 - + - 8.0.4 + 8.0.5 diff --git a/eng/Baseline.xml b/eng/Baseline.xml index 110083387ae6..7d886afd31fc 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -4,110 +4,110 @@ This file contains a list of all the packages and their versions which were rele Update this list when preparing for a new patch. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8165a50d2627..66d02fcb8180 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,37 +9,37 @@ --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - f42a208554fed9bb37603c5ed4f02206b2b1b9fa + 0d1256be4658567c8a24b4c027bdbb3dbd6de656 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -53,9 +53,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -65,9 +65,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 5535e31a712343a63f5d7d796cd874e563e5ac14 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -121,9 +121,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -185,13 +185,13 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://github.com/dotnet/source-build-externals - 300e99190e6ae1983681694dbdd5f75f0c692081 + 4f2151df120194f0268944f1b723c14820738fc8 @@ -223,9 +223,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 5535e31a712343a63f5d7d796cd874e563e5ac14 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -241,7 +241,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -255,9 +255,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 9f4b1f5d664afdfc80e1508ab7ed099dff210fbd + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -271,21 +271,21 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 5535e31a712343a63f5d7d796cd874e563e5ac14 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -316,31 +316,31 @@ Win-x64 is used here because we have picked an arbitrary runtime identifier to flow the version of the latest NETCore.App runtime. All Runtime.$rid packages should have the same version. --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://github.com/dotnet/xdt 9a1c3e1b7f0c8763d4c96e593961a61a72679a7b - + https://github.com/dotnet/source-build-reference-packages - 79827eed138fd2575a8b24820b4f385ee4ffb6e6 + 6ed73280a6d70f7e7ac39c86f2abe8c10983f0bb @@ -368,34 +368,34 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 087e15321bb712ef6fe8b0ba6f8bd12facf92629 + 2aade6beb02ea367fd97c4070a4198802fe61c03 https://github.com/dotnet/winforms abda8e3bfa78319363526b5a5f86863ec979940e - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac - + https://github.com/dotnet/arcade - 188340e12c0a372b1681ad6a5e72c608021efdba + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac https://github.com/dotnet/extensions diff --git a/eng/Versions.props b/eng/Versions.props index 138a9144ab30..74cc2f6bc296 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,11 +8,12 @@ 8 0 - 5 + 7 - true - 7.1.2 + false + 7.1.2 + *-* @@ -65,20 +66,20 @@ --> - 8.0.0 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5-servicing.24216.15 + 8.0.1 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7-servicing.24313.11 8.0.0 8.0.0 8.0.0 - 8.0.1 + 8.0.2 8.0.0 8.0.0 - 8.0.0 + 8.0.1 8.0.0 8.0.0 8.0.0 @@ -92,7 +93,7 @@ 8.0.0 8.0.0 8.0.0 - 8.0.5-servicing.24216.15 + 8.0.7-servicing.24313.11 8.0.0 8.0.0 8.0.0 @@ -108,7 +109,7 @@ 8.0.0 8.0.2 8.0.0 - 8.0.5-servicing.24216.15 + 8.0.7-servicing.24313.11 8.0.0 8.0.1 8.0.0 @@ -116,7 +117,7 @@ 8.0.0-rtm.23520.14 8.0.0 8.0.0 - 8.0.0 + 8.0.1 8.0.0 8.0.0 8.0.0 @@ -124,13 +125,13 @@ 8.0.0 8.0.0 8.0.0 - 8.0.3 + 8.0.4 8.0.0 8.0.0 8.0.0 - 8.0.5-servicing.24216.15 + 8.0.7-servicing.24313.11 - 8.0.5-servicing.24216.15 + 8.0.7-servicing.24313.11 8.0.0 8.0.1 @@ -142,14 +143,14 @@ 8.1.0-preview.23604.1 8.1.0-preview.23604.1 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 - 8.0.5 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 + 8.0.7 4.8.0-3.23518.7 4.8.0-3.23518.7 @@ -161,13 +162,13 @@ 6.2.4 6.2.4 - 8.0.0-beta.24204.3 - 8.0.0-beta.24204.3 - 8.0.0-beta.24204.3 + 8.0.0-beta.24266.3 + 8.0.0-beta.24266.3 + 8.0.0-beta.24266.3 - 8.0.0-alpha.1.24175.3 + 8.0.0-alpha.1.24269.1 - 8.0.0-alpha.1.24163.3 + 8.0.0-alpha.1.24257.2 2.0.0-beta-23228-03 diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml index f0513aee5b0d..43ee0c202fc7 100644 --- a/eng/common/templates-official/job/source-index-stage1.yml +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -1,6 +1,7 @@ parameters: runAsPublic: false - sourceIndexPackageVersion: 1.0.1-20230228.2 + sourceIndexUploadPackageVersion: 2.0.0-20240502.12 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20240129.2 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] @@ -14,15 +15,15 @@ jobs: dependsOn: ${{ parameters.dependsOn }} condition: ${{ parameters.condition }} variables: - - name: SourceIndexPackageVersion - value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexUploadPackageVersion + value: ${{ parameters.sourceIndexUploadPackageVersion }} + - name: SourceIndexProcessBinlogPackageVersion + value: ${{ parameters.sourceIndexProcessBinlogPackageVersion }} - name: SourceIndexPackageSource value: ${{ parameters.sourceIndexPackageSource }} - name: BinlogPath value: ${{ parameters.binlogPath }} - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - group: source-dot-net stage1 variables - - template: /eng/common/templates-official/variables/pool-providers.yml + - template: /eng/common/templates/variables/pool-providers.yml ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} @@ -33,24 +34,23 @@ jobs: demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: windows.vs2022.amd64 - os: windows + demands: ImageOverride -equals windows.vs2019.amd64 steps: - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - task: UseDotNet@2 - displayName: Use .NET Core SDK 6 + displayName: Use .NET 8 SDK inputs: packageType: sdk - version: 6.0.x + version: 8.0.x installationPath: $(Agent.TempDirectory)/dotnet workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(sourceIndexProcessBinlogPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(sourceIndexUploadPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: Download Tools # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) @@ -62,7 +62,24 @@ jobs: displayName: Process Binlog into indexable sln - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) - displayName: Upload stage1 artifacts to source index - env: - BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) + - task: AzureCLI@2 + displayName: Get stage 1 auth token + inputs: + azureSubscription: 'SourceDotNet Stage1 Publish' + addSpnToEnvironment: true + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + echo "##vso[task.setvariable variable=ARM_CLIENT_ID]$env:servicePrincipalId" + echo "##vso[task.setvariable variable=ARM_ID_TOKEN]$env:idToken" + echo "##vso[task.setvariable variable=ARM_TENANT_ID]$env:tenantId" + + - script: | + echo "Client ID: $(ARM_CLIENT_ID)" + echo "ID Token: $(ARM_ID_TOKEN)" + echo "Tenant ID: $(ARM_TENANT_ID)" + az login --service-principal -u $(ARM_CLIENT_ID) --tenant $(ARM_TENANT_ID) --allow-no-subscriptions --federated-token $(ARM_ID_TOKEN) + displayName: "Login to Azure" + + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 + displayName: Upload stage1 artifacts to source index \ No newline at end of file diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml index b98202aa02d8..43ee0c202fc7 100644 --- a/eng/common/templates/job/source-index-stage1.yml +++ b/eng/common/templates/job/source-index-stage1.yml @@ -1,6 +1,7 @@ parameters: runAsPublic: false - sourceIndexPackageVersion: 1.0.1-20230228.2 + sourceIndexUploadPackageVersion: 2.0.0-20240502.12 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20240129.2 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] @@ -14,14 +15,14 @@ jobs: dependsOn: ${{ parameters.dependsOn }} condition: ${{ parameters.condition }} variables: - - name: SourceIndexPackageVersion - value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexUploadPackageVersion + value: ${{ parameters.sourceIndexUploadPackageVersion }} + - name: SourceIndexProcessBinlogPackageVersion + value: ${{ parameters.sourceIndexProcessBinlogPackageVersion }} - name: SourceIndexPackageSource value: ${{ parameters.sourceIndexPackageSource }} - name: BinlogPath value: ${{ parameters.binlogPath }} - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - group: source-dot-net stage1 variables - template: /eng/common/templates/variables/pool-providers.yml ${{ if ne(parameters.pool, '') }}: @@ -40,16 +41,16 @@ jobs: - ${{ preStep }} - task: UseDotNet@2 - displayName: Use .NET Core SDK 6 + displayName: Use .NET 8 SDK inputs: packageType: sdk - version: 6.0.x + version: 8.0.x installationPath: $(Agent.TempDirectory)/dotnet workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(sourceIndexProcessBinlogPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(sourceIndexUploadPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: Download Tools # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) @@ -61,7 +62,24 @@ jobs: displayName: Process Binlog into indexable sln - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) - displayName: Upload stage1 artifacts to source index - env: - BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) + - task: AzureCLI@2 + displayName: Get stage 1 auth token + inputs: + azureSubscription: 'SourceDotNet Stage1 Publish' + addSpnToEnvironment: true + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + echo "##vso[task.setvariable variable=ARM_CLIENT_ID]$env:servicePrincipalId" + echo "##vso[task.setvariable variable=ARM_ID_TOKEN]$env:idToken" + echo "##vso[task.setvariable variable=ARM_TENANT_ID]$env:tenantId" + + - script: | + echo "Client ID: $(ARM_CLIENT_ID)" + echo "ID Token: $(ARM_ID_TOKEN)" + echo "Tenant ID: $(ARM_TENANT_ID)" + az login --service-principal -u $(ARM_CLIENT_ID) --tenant $(ARM_TENANT_ID) --allow-no-subscriptions --federated-token $(ARM_ID_TOKEN) + displayName: "Login to Azure" + + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 + displayName: Upload stage1 artifacts to source index \ No newline at end of file diff --git a/eng/helix/helix.proj b/eng/helix/helix.proj index 73ed73a32068..99254551279f 100644 --- a/eng/helix/helix.proj +++ b/eng/helix/helix.proj @@ -21,7 +21,7 @@ - + diff --git a/eng/scripts/SetupIdentitySources.ps1 b/eng/scripts/SetupIdentitySources.ps1 new file mode 100644 index 000000000000..58a4e690d7b1 --- /dev/null +++ b/eng/scripts/SetupIdentitySources.ps1 @@ -0,0 +1,46 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)][string]$ConfigFile, + [Parameter(Mandatory = $true)][string]$IdentityModelPackageSource +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# Add source entry to PackageSources +function AddPackageSource($sources, $SourceName, $SourceEndPoint) { + $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") + + if ($packageSource -eq $null) + { + $packageSource = $doc.CreateElement("add") + $packageSource.SetAttribute("key", $SourceName) + $packageSource.SetAttribute("value", $SourceEndPoint) + $sources.AppendChild($packageSource) | Out-Null + } + else { + Write-Host "Package source $SourceName already present." + } +} + +if (!(Test-Path $ConfigFile -PathType Leaf)) { + Write-PipelineTelemetryError -Category 'Build' -Message "eng/scripts/SetupIdentitySources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +} + +# Load NuGet.config +$doc = New-Object System.Xml.XmlDocument +$filename = (Get-Item $ConfigFile).FullName +$doc.Load($filename) + +# Get reference to or create one if none exist already +$sources = $doc.DocumentElement.SelectSingleNode("packageSources") +if ($sources -eq $null) { + $sources = $doc.CreateElement("packageSources") + $doc.DocumentElement.AppendChild($sources) | Out-Null +} + +AddPackageSource -Sources $sources -SourceName "identitymodel-nightlies" -SourceEndPoint $IdentityModelPackageSource + +$doc.Save($filename) \ No newline at end of file diff --git a/eng/targets/Helix.targets b/eng/targets/Helix.targets index 35c116026c62..ee73eb8ac8a3 100644 --- a/eng/targets/Helix.targets +++ b/eng/targets/Helix.targets @@ -141,7 +141,7 @@ <_Temp Include="@(HelixAvailableTargetQueue)" /> - + diff --git a/global.json b/global.json index 915af8cbaa3c..bfd92fee2223 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "8.0.104" + "version": "8.0.105" }, "tools": { - "dotnet": "8.0.104", + "dotnet": "8.0.105", "runtimes": { "dotnet/x86": [ "$(MicrosoftNETCoreBrowserDebugHostTransportVersion)" @@ -27,7 +27,7 @@ }, "msbuild-sdks": { "Yarn.MSBuild": "1.22.19", - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24266.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24266.3" } } diff --git a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj index dbc464028962..fe1c0f19275e 100644 --- a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj +++ b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj b/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj index 8267a699183b..ebd48678889a 100644 --- a/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj +++ b/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs index 8d26f10f728f..bc84fb87dfe7 100644 --- a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs @@ -69,12 +69,8 @@ public void RequestEnd(string protocol, bool isHttps, string scheme, string meth { tags.Add("http.route", route); } - // This exception is only present if there is an unhandled exception. - // An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add error.type to custom tags. - if (exception != null) - { - tags.Add("error.type", exception.GetType().FullName); - } + + // Add before some built in tags so custom tags are prioritized when dealing with duplicates. if (customTags != null) { for (var i = 0; i < customTags.Count; i++) @@ -83,6 +79,15 @@ public void RequestEnd(string protocol, bool isHttps, string scheme, string meth } } + // This exception is only present if there is an unhandled exception. + // An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add error.type to custom tags. + if (exception != null) + { + // Exception tag could have been added by middleware. If an exception is later thrown in request pipeline + // then we don't want to add a duplicate tag here because that breaks some metrics systems. + tags.TryAddTag("error.type", exception.GetType().FullName); + } + var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); _requestDuration.Record(duration.TotalSeconds, tags); } diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index c898aed6af58..3caa14c4beb3 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Installers/Windows/SharedFramework/Product.wxs b/src/Installers/Windows/SharedFramework/Product.wxs index ae95ee10ae9c..30d8d6025817 100644 --- a/src/Installers/Windows/SharedFramework/Product.wxs +++ b/src/Installers/Windows/SharedFramework/Product.wxs @@ -60,6 +60,7 @@ + @@ -73,6 +74,12 @@ + + + + + + @@ -110,5 +117,11 @@ + + + + + + diff --git a/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs b/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs index aace88526805..36ab75c70ad2 100644 --- a/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs +++ b/src/Middleware/Diagnostics/src/DiagnosticsTelemetry.cs @@ -15,7 +15,9 @@ public static void ReportUnhandledException(ILogger logger, HttpContext context, if (context.Features.Get() is { } tagsFeature) { - tagsFeature.Tags.Add(new KeyValuePair("error.type", ex.GetType().FullName)); + // Multiple exception middleware could be registered that have already added the tag. + // We don't want to add a duplicate tag here because that breaks some metrics systems. + tagsFeature.TryAddTag("error.type", ex.GetType().FullName); } } } diff --git a/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj b/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj index 4657b64cbf7b..8671a3b03fb4 100644 --- a/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj +++ b/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Middleware/Diagnostics/test/FunctionalTests/ExceptionHandlerSampleTest.cs b/src/Middleware/Diagnostics/test/FunctionalTests/ExceptionHandlerSampleTest.cs index aea061e0c4c7..20a21449f879 100644 --- a/src/Middleware/Diagnostics/test/FunctionalTests/ExceptionHandlerSampleTest.cs +++ b/src/Middleware/Diagnostics/test/FunctionalTests/ExceptionHandlerSampleTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net; diff --git a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs index 7119b0bf7468..4ff8342e0957 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using System.Net.Http; namespace Microsoft.AspNetCore.Diagnostics; @@ -965,4 +966,136 @@ public async Task UnhandledError_ExceptionNameTagAdded() Assert.Equal("System.Exception", (string)m.Tags["error.type"]); }); } + + [Fact] + public async Task UnhandledError_MultipleHandlers_ExceptionNameTagAddedOnce() + { + // Arrange + var meterFactory = new TestMeterFactory(); + using var instrumentCollector = new MetricCollector(meterFactory, "Microsoft.AspNetCore.Hosting", "http.server.request.duration"); + + using var host = new HostBuilder() + .ConfigureServices(s => + { + s.AddSingleton(meterFactory); + }) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + // Second error and handler + app.UseExceptionHandler(new ExceptionHandlerOptions() + { + ExceptionHandler = httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + return Task.CompletedTask; + } + }); + app.Use(async (context, next) => + { + await next(); + throw new InvalidOperationException("Test exception2"); + }); + + // First error and handler + app.UseExceptionHandler(new ExceptionHandlerOptions() + { + ExceptionHandler = httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status404NotFound; + return Task.CompletedTask; + } + }); + app.Run(context => + { + throw new Exception("Test exception1"); + }); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + + // Act + var response = await server.CreateClient().GetAsync("/path"); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + await instrumentCollector.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); + + // Assert + Assert.Collection( + instrumentCollector.GetMeasurementSnapshot(), + m => + { + Assert.True(m.Value > 0); + Assert.Equal(500, (int)m.Tags["http.response.status_code"]); + Assert.Equal("System.Exception", (string)m.Tags["error.type"]); + }); + } + + [Fact] + public async Task UnhandledError_ErrorAfterHandler_ExceptionNameTagAddedOnce() + { + // Arrange + var meterFactory = new TestMeterFactory(); + using var instrumentCollector = new MetricCollector(meterFactory, "Microsoft.AspNetCore.Hosting", "http.server.request.duration"); + + using var host = new HostBuilder() + .ConfigureServices(s => + { + s.AddSingleton(meterFactory); + }) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + // Second error + app.Use(async (context, next) => + { + await next(); + + throw new InvalidOperationException("Test exception2"); + }); + + // First error and handler + app.UseExceptionHandler(new ExceptionHandlerOptions() + { + ExceptionHandler = httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status404NotFound; + return httpContext.Response.WriteAsync("Custom handler"); + } + }); + app.Run(context => + { + throw new Exception("Test exception1"); + }); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + + // Act + await Assert.ThrowsAsync(async () => await server.CreateClient().GetAsync("/path")); + + await instrumentCollector.WaitForMeasurementsAsync(minCount: 1).DefaultTimeout(); + + // Assert + Assert.Collection( + instrumentCollector.GetMeasurementSnapshot(), + m => + { + Assert.True(m.Value > 0); + Assert.Equal(404, (int)m.Tags["http.response.status_code"]); + Assert.Equal("System.Exception", (string)m.Tags["error.type"]); + }); + } } diff --git a/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/ExceptionHandlerSample.csproj b/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/ExceptionHandlerSample.csproj index 2faf636c4914..e2eaca2faeda 100644 --- a/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/ExceptionHandlerSample.csproj +++ b/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/ExceptionHandlerSample.csproj @@ -10,5 +10,6 @@ + diff --git a/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/StartupWithWebSocket.cs b/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/StartupWithWebSocket.cs new file mode 100644 index 000000000000..eb2fc7b4a956 --- /dev/null +++ b/src/Middleware/Diagnostics/test/testassets/ExceptionHandlerSample/StartupWithWebSocket.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http.Metadata; + +namespace ExceptionHandlerSample; + +// Note that this class isn't used in tests as TestServer doesn't have the right behavior to test web sockets +// in the way we need. But leaving here so it can be used in Program.cs when starting the app manually. +public class StartupWithWebSocket +{ + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app) + { + app.UseExceptionHandler(options => { }); // Exception handling middleware introduces duplicate tag + app.UseWebSockets(); + + app.Use(async (HttpContext context, Func next) => + { + try + { + if (context.WebSockets.IsWebSocketRequest) + { + using var ws = await context.WebSockets.AcceptWebSocketAsync(); + await ws.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes("Hello")), System.Net.WebSockets.WebSocketMessageType.Text, true, context.RequestAborted); + await ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "done", context.RequestAborted); + throw new InvalidOperationException("Throw after websocket request completion to produce the bug"); + } + else + { + await context.Response.WriteAsync($"Not a web socket request. PID: {Process.GetCurrentProcess().Id}"); + } + } + catch (Exception ex) + { + _ = ex; + throw; + } + }); + } +} + diff --git a/src/ProjectTemplates/test/Templates.Blazor.Tests/Templates.Blazor.Tests.csproj b/src/ProjectTemplates/test/Templates.Blazor.Tests/Templates.Blazor.Tests.csproj index 368459e98c90..1db85d5286fe 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.Tests/Templates.Blazor.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Blazor.Tests/Templates.Blazor.Tests.csproj @@ -9,6 +9,7 @@ true $(RunTemplateTests) true + false diff --git a/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Auth.Tests/Templates.Blazor.WebAssembly.Auth.Tests.csproj b/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Auth.Tests/Templates.Blazor.WebAssembly.Auth.Tests.csproj index 3754aee3abd4..3d0831a1001a 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Auth.Tests/Templates.Blazor.WebAssembly.Auth.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Auth.Tests/Templates.Blazor.WebAssembly.Auth.Tests.csproj @@ -9,6 +9,7 @@ true true + false diff --git a/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Tests/Templates.Blazor.WebAssembly.Tests.csproj b/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Tests/Templates.Blazor.WebAssembly.Tests.csproj index 44348f90847a..b42866c3a78f 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Tests/Templates.Blazor.WebAssembly.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Blazor.WebAssembly.Tests/Templates.Blazor.WebAssembly.Tests.csproj @@ -9,6 +9,7 @@ true true + false diff --git a/src/ProjectTemplates/test/Templates.Mvc.Tests/Templates.Mvc.Tests.csproj b/src/ProjectTemplates/test/Templates.Mvc.Tests/Templates.Mvc.Tests.csproj index 880e1bb68d6b..152c0d7b0d07 100644 --- a/src/ProjectTemplates/test/Templates.Mvc.Tests/Templates.Mvc.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Mvc.Tests/Templates.Mvc.Tests.csproj @@ -9,6 +9,7 @@ true true + false diff --git a/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj b/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj index d7c3c354d144..fb297bfae7c5 100644 --- a/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj +++ b/src/ProjectTemplates/test/Templates.Tests/Templates.Tests.csproj @@ -9,6 +9,7 @@ true true + false diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerConfigureOptions.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerConfigureOptions.cs index 812eb9287aa3..97568fde9751 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerConfigureOptions.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerConfigureOptions.cs @@ -40,12 +40,14 @@ public void Configure(string? name, JwtBearerOptions options) return; } + var validateIssuer = StringHelpers.ParseValueOrDefault(configSection[nameof(TokenValidationParameters.ValidateIssuer)], bool.Parse, options.TokenValidationParameters.ValidateIssuer); var issuer = configSection[nameof(TokenValidationParameters.ValidIssuer)]; var issuers = configSection.GetSection(nameof(TokenValidationParameters.ValidIssuers)).GetChildren().Select(iss => iss.Value).ToList(); if (issuer is not null) { issuers.Add(issuer); } + var validateAudience = StringHelpers.ParseValueOrDefault(configSection[nameof(TokenValidationParameters.ValidateAudience)], bool.Parse, options.TokenValidationParameters.ValidateAudience); var audience = configSection[nameof(TokenValidationParameters.ValidAudience)]; var audiences = configSection.GetSection(nameof(TokenValidationParameters.ValidAudiences)).GetChildren().Select(aud => aud.Value).ToList(); if (audience is not null) @@ -71,9 +73,9 @@ public void Configure(string? name, JwtBearerOptions options) options.SaveToken = StringHelpers.ParseValueOrDefault(configSection[nameof(options.SaveToken)], bool.Parse, options.SaveToken); options.TokenValidationParameters = new() { - ValidateIssuer = issuers.Count > 0, + ValidateIssuer = validateIssuer, ValidIssuers = issuers, - ValidateAudience = audiences.Count > 0, + ValidateAudience = validateAudience, ValidAudiences = audiences, ValidateIssuerSigningKey = true, IssuerSigningKeys = GetIssuerSigningKeys(configSection, issuers), diff --git a/src/Security/Authentication/test/JwtBearerTests_Handler.cs b/src/Security/Authentication/test/JwtBearerTests_Handler.cs index 6d8260c2a39f..60001e54f3cd 100644 --- a/src/Security/Authentication/test/JwtBearerTests_Handler.cs +++ b/src/Security/Authentication/test/JwtBearerTests_Handler.cs @@ -31,17 +31,9 @@ public class JwtBearerTests_Handler : SharedAuthenticationTests false; } protected override bool SupportsSignOut { get => false; } - protected override void RegisterAuth(AuthenticationBuilder services, Action configure) - { - services.AddJwtBearer(o => - { - ConfigureDefaults(o); - configure.Invoke(o); - }); - } - - private void ConfigureDefaults(JwtBearerOptions o) + protected override void RegisterAuth(AuthenticationBuilder services, Action configure = null) { + services.AddJwtBearer(configure); } [Fact] @@ -964,25 +956,19 @@ public async Task ExpirationAndIssuedWhenMinOrMaxValue() [Fact] public void CanReadJwtBearerOptionsFromConfig() { - var services = new ServiceCollection().AddLogging(); - var config = new ConfigurationBuilder().AddInMemoryCollection(new[] - { - new KeyValuePair("Authentication:Schemes:Bearer:ValidIssuer", "dotnet-user-jwts"), - new KeyValuePair("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5000"), - new KeyValuePair("Authentication:Schemes:Bearer:ValidAudiences:1", "https://localhost:5001"), - new KeyValuePair("Authentication:Schemes:Bearer:BackchannelTimeout", "00:01:00"), - new KeyValuePair("Authentication:Schemes:Bearer:RequireHttpsMetadata", "false"), - new KeyValuePair("Authentication:Schemes:Bearer:SaveToken", "True"), - }).Build(); + var services = new ServiceCollection(); + var config = new ConfigurationBuilder().AddInMemoryCollection([ + new("Authentication:Schemes:Bearer:ValidIssuer", "dotnet-user-jwts"), + new("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5000"), + new("Authentication:Schemes:Bearer:ValidAudiences:1", "https://localhost:5001"), + new("Authentication:Schemes:Bearer:BackchannelTimeout", "00:01:00"), + new("Authentication:Schemes:Bearer:RequireHttpsMetadata", "false"), + new("Authentication:Schemes:Bearer:SaveToken", "True"), + ]).Build(); services.AddSingleton(config); // Act - var builder = services.AddAuthentication(o => - { - o.AddScheme("Bearer", "Bearer"); - }); - builder.AddJwtBearer("Bearer"); - RegisterAuth(builder, _ => { }); + RegisterAuth(services.AddAuthentication()); var sp = services.BuildServiceProvider(); // Assert @@ -992,35 +978,34 @@ public void CanReadJwtBearerOptionsFromConfig() Assert.Equal(jwtBearerOptions.BackchannelTimeout, TimeSpan.FromSeconds(60)); Assert.False(jwtBearerOptions.RequireHttpsMetadata); Assert.True(jwtBearerOptions.SaveToken); - Assert.True(jwtBearerOptions.MapInboundClaims); // Assert default values are respected + // ValidateIssuerSigningKey should always be set to its non-default value of true if options are read from config. + Assert.True(jwtBearerOptions.TokenValidationParameters.ValidateIssuerSigningKey); + // Assert default values for other options are respected. + Assert.True(jwtBearerOptions.MapInboundClaims); + Assert.True(jwtBearerOptions.TokenValidationParameters.ValidateIssuer); + Assert.True(jwtBearerOptions.TokenValidationParameters.ValidateAudience); } [Fact] public void CanReadMultipleIssuersFromConfig() { - var services = new ServiceCollection().AddLogging(); + var services = new ServiceCollection(); var firstKey = "qPG6tDtfxFYZifHW3sEueQ=="; var secondKey = "6JPzXj6aOPdojlZdeLshaA=="; - var config = new ConfigurationBuilder().AddInMemoryCollection(new[] - { - new KeyValuePair("Authentication:Schemes:Bearer:ValidIssuers:0", "dotnet-user-jwts"), - new KeyValuePair("Authentication:Schemes:Bearer:ValidIssuers:1", "dotnet-user-jwts-2"), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:0:Issuer", "dotnet-user-jwts"), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:0:Value", firstKey), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:0:Length", "32"), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:1:Issuer", "dotnet-user-jwts-2"), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:1:Value", secondKey), - new KeyValuePair("Authentication:Schemes:Bearer:SigningKeys:1:Length", "32"), - }).Build(); + var config = new ConfigurationBuilder().AddInMemoryCollection([ + new("Authentication:Schemes:Bearer:ValidIssuers:0", "dotnet-user-jwts"), + new("Authentication:Schemes:Bearer:ValidIssuers:1", "dotnet-user-jwts-2"), + new("Authentication:Schemes:Bearer:SigningKeys:0:Issuer", "dotnet-user-jwts"), + new("Authentication:Schemes:Bearer:SigningKeys:0:Value", firstKey), + new("Authentication:Schemes:Bearer:SigningKeys:0:Length", "32"), + new("Authentication:Schemes:Bearer:SigningKeys:1:Issuer", "dotnet-user-jwts-2"), + new("Authentication:Schemes:Bearer:SigningKeys:1:Value", secondKey), + new("Authentication:Schemes:Bearer:SigningKeys:1:Length", "32"), + ]).Build(); services.AddSingleton(config); // Act - var builder = services.AddAuthentication(o => - { - o.AddScheme("Bearer", "Bearer"); - }); - builder.AddJwtBearer("Bearer"); - RegisterAuth(builder, _ => { }); + RegisterAuth(services.AddAuthentication()); var sp = services.BuildServiceProvider(); // Assert @@ -1030,6 +1015,48 @@ public void CanReadMultipleIssuersFromConfig() Assert.Equal(secondKey, Convert.ToBase64String(jwtBearerOptions.TokenValidationParameters.IssuerSigningKeys.OfType().LastOrDefault()?.Key)); } + [Fact] + public void IssuerAndAudienceValidationEnabledByDefaultWhenOptionsAreReadFromConfig() + { + var services = new ServiceCollection(); + var config = new ConfigurationBuilder().AddInMemoryCollection([ + new("Authentication:Schemes:Bearer:Authority", "https://localhost:5001"), + ]).Build(); + services.AddSingleton(config); + + // Act + RegisterAuth(services.AddAuthentication()); + var sp = services.BuildServiceProvider(); + + // Assert + var jwtBearerOptions = sp.GetRequiredService>().Get(JwtBearerDefaults.AuthenticationScheme); + Assert.Equal("https://localhost:5001", jwtBearerOptions.Authority); + Assert.True(jwtBearerOptions.TokenValidationParameters.ValidateIssuer); + Assert.True(jwtBearerOptions.TokenValidationParameters.ValidateAudience); + } + + [Fact] + public void IssuerAndAudienceValidationCanBeDisabledFromConfig() + { + var services = new ServiceCollection(); + var config = new ConfigurationBuilder().AddInMemoryCollection([ + new("Authentication:Schemes:Bearer:Authority", "https://localhost:5001"), + new("Authentication:Schemes:Bearer:ValidateIssuer", "false"), + new("Authentication:Schemes:Bearer:ValidateAudience", "false"), + ]).Build(); + services.AddSingleton(config); + + // Act + RegisterAuth(services.AddAuthentication()); + var sp = services.BuildServiceProvider(); + + // Assert + var jwtBearerOptions = sp.GetRequiredService>().Get(JwtBearerDefaults.AuthenticationScheme); + Assert.Equal("https://localhost:5001", jwtBearerOptions.Authority); + Assert.False(jwtBearerOptions.TokenValidationParameters.ValidateIssuer); + Assert.False(jwtBearerOptions.TokenValidationParameters.ValidateAudience); + } + class InvalidTokenValidator : TokenHandler { public InvalidTokenValidator() diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index a1322eaabb32..fcd4205138c9 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -22,7 +22,8 @@ const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L" HandlerResolver::HandlerResolver(HMODULE hModule, const IHttpServer &pServer) : m_hModule(hModule), m_pServer(pServer), - m_loadedApplicationHostingModel(HOSTING_UNKNOWN) + m_loadedApplicationHostingModel(HOSTING_UNKNOWN), + m_shutdownDelay() { m_disallowRotationOnConfigChange = false; InitializeSRWLock(&m_requestHandlerLoadLock); @@ -171,6 +172,7 @@ HandlerResolver::GetApplicationFactory(const IHttpApplication& pApplication, con m_loadedApplicationHostingModel = options.QueryHostingModel(); m_loadedApplicationId = pApplication.GetApplicationId(); m_disallowRotationOnConfigChange = options.QueryDisallowRotationOnConfigChange(); + m_shutdownDelay = options.QueryShutdownDelay(); RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, shadowCopyPath, options, pApplicationFactory, errorContext)); @@ -197,6 +199,11 @@ bool HandlerResolver::GetDisallowRotationOnConfigChange() return m_disallowRotationOnConfigChange; } +std::chrono::milliseconds HandlerResolver::GetShutdownDelay() const +{ + return m_shutdownDelay; +} + HRESULT HandlerResolver::FindNativeAssemblyFromGlobalLocation( const ShimOptions& pConfiguration, diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h index a828773c20e1..54121f072cac 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -19,6 +19,7 @@ class HandlerResolver void ResetHostingModel(); APP_HOSTING_MODEL GetHostingModel(); bool GetDisallowRotationOnConfigChange(); + std::chrono::milliseconds GetShutdownDelay() const; private: HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const std::filesystem::path& shadowCopyPath, const ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory, ErrorContext& errorContext); @@ -40,6 +41,7 @@ class HandlerResolver APP_HOSTING_MODEL m_loadedApplicationHostingModel; HostFxr m_hHostFxrDll; bool m_disallowRotationOnConfigChange; + std::chrono::milliseconds m_shutdownDelay; static const PCWSTR s_pwzAspnetcoreInProcessRequestHandlerName; static const PCWSTR s_pwzAspnetcoreOutOfProcessRequestHandlerName; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index f85f5483a2c8..e538e3e8e9fa 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -12,6 +12,8 @@ #define CS_ASPNETCORE_SHADOW_COPY_DIRECTORY L"shadowCopyDirectory" #define CS_ASPNETCORE_CLEAN_SHADOW_DIRECTORY_CONTENT L"cleanShadowCopyDirectory" #define CS_ASPNETCORE_DISALLOW_ROTATE_CONFIG L"disallowRotationOnConfigChange" +#define CS_ASPNETCORE_SHUTDOWN_DELAY L"shutdownDelay" +#define CS_ASPNETCORE_SHUTDOWN_DELAY_ENV L"ANCM_shutdownDelay" ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : m_hostingModel(HOSTING_UNKNOWN), @@ -53,7 +55,7 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : auto disallowRotationOnConfigChange = find_element(handlerSettings, CS_ASPNETCORE_DISALLOW_ROTATE_CONFIG).value_or(std::wstring()); m_fDisallowRotationOnConfigChange = equals_ignore_case(L"true", disallowRotationOnConfigChange); - + m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); @@ -82,4 +84,38 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : auto dotnetEnvironmentEnabled = equals_ignore_case(L"Development", dotnetEnvironment); m_fShowDetailedErrors = detailedErrorsEnabled || aspnetCoreEnvironmentEnabled || dotnetEnvironmentEnabled; + + // Specifies how long to delay (in milliseconds) after IIS tells us to stop before starting the application shutdown. + // See StartShutdown in globalmodule to see how it's used. + auto shutdownDelay = find_element(handlerSettings, CS_ASPNETCORE_SHUTDOWN_DELAY).value_or(std::wstring()); + if (shutdownDelay.empty()) + { + // Fallback to environment variable if process specific config wasn't set + shutdownDelay = Environment::GetEnvironmentVariableValue(CS_ASPNETCORE_SHUTDOWN_DELAY_ENV) + .value_or(environmentVariables[CS_ASPNETCORE_SHUTDOWN_DELAY_ENV]); + if (shutdownDelay.empty()) + { + // Default if neither process specific config or environment variable aren't set + m_fShutdownDelay = std::chrono::seconds(0); + } + else + { + SetShutdownDelay(shutdownDelay); + } + } + else + { + SetShutdownDelay(shutdownDelay); + } +} + +void ShimOptions::SetShutdownDelay(const std::wstring& shutdownDelay) +{ + auto millsecondsValue = std::stoi(shutdownDelay); + if (millsecondsValue < 0) + { + throw ConfigurationLoadException(format( + L"'shutdownDelay' in web.config or '%s' environment variable is less than 0.", CS_ASPNETCORE_SHUTDOWN_DELAY_ENV)); + } + m_fShutdownDelay = std::chrono::milliseconds(millsecondsValue); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h index 5b3cf72d692b..4e13190be6dd 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.h @@ -89,6 +89,12 @@ class ShimOptions: NonCopyable return m_fDisallowRotationOnConfigChange; } + std::chrono::milliseconds + QueryShutdownDelay() const noexcept + { + return m_fShutdownDelay; + } + ShimOptions(const ConfigurationSource &configurationSource); private: @@ -104,4 +110,7 @@ class ShimOptions: NonCopyable bool m_fCleanShadowCopyDirectory; bool m_fDisallowRotationOnConfigChange; std::wstring m_strShadowCopyingDirectory; + std::chrono::milliseconds m_fShutdownDelay; + + void SetShutdownDelay(const std::wstring& shutdownDelay); }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index 1da43e14343a..48855946d29a 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -143,22 +143,26 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( } } - // If we receive a request at this point. - // OutOfProcess: we will create a new application with new configuration - // InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess - // on the worker process - if (!applicationsToRecycle.empty()) { for (auto& application : applicationsToRecycle) { try { - application->ShutDownApplication(/* fServerInitiated */ false); + if (UseLegacyShutdown()) + { + application->ShutDownApplication(/* fServerInitiated */ false); + } + else + { + // Recycle the process to trigger OnGlobalStopListening + // which will shutdown the server and stop listening for new requests for this app + m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); + } } catch (...) { - LOG_ERRORF(L"Failed to stop application '%ls'", application->QueryApplicationInfoKey().c_str()); + LOG_ERRORF(L"Failed to recycle application '%ls'", application->QueryApplicationInfoKey().c_str()); OBSERVE_CAUGHT_EXCEPTION() // Failed to recycle an application. Log an event @@ -176,28 +180,31 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( } } - // Remove apps after calling shutdown on each of them - // This is exclusive to in-process, as the shutdown of an in-process app recycles - // the entire worker process. - if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) + if (UseLegacyShutdown()) { - SRWExclusiveLock lock(m_srwLock); - const std::wstring configurationPath = pszApplicationId; - - auto itr = m_pApplicationInfoHash.begin(); - while (itr != m_pApplicationInfoHash.end()) + // Remove apps after calling shutdown on each of them + // This is exclusive to in-process, as the shutdown of an in-process app recycles + // the entire worker process. + if (m_handlerResolver.GetHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS) { - if (itr->second != nullptr && itr->second->ConfigurationPathApplies(configurationPath) - && std::find(applicationsToRecycle.begin(), applicationsToRecycle.end(), itr->second) != applicationsToRecycle.end()) - { - itr = m_pApplicationInfoHash.erase(itr); - } - else + SRWExclusiveLock lock(m_srwLock); + const std::wstring configurationPath = pszApplicationId; + + auto itr = m_pApplicationInfoHash.begin(); + while (itr != m_pApplicationInfoHash.end()) { - ++itr; + if (itr->second != nullptr && itr->second->ConfigurationPathApplies(configurationPath) + && std::find(applicationsToRecycle.begin(), applicationsToRecycle.end(), itr->second) != applicationsToRecycle.end()) + { + itr = m_pApplicationInfoHash.erase(itr); + } + else + { + ++itr; + } } - } - } // Release Exclusive m_srwLock + } // Release Exclusive m_srwLock + } } CATCH_RETURN() @@ -211,14 +218,19 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( VOID APPLICATION_MANAGER::ShutDown() { + // During shutdown we lock until we delete the application + SRWExclusiveLock lock(m_srwLock); + // We are guaranteed to only have one outstanding OnGlobalStopListening event at a time // However, it is possible to receive multiple OnGlobalStopListening events // Protect against this by checking if we already shut down. + if (g_fInShutdown) + { + return; + } + g_fInShutdown = TRUE; g_fInAppOfflineShutdown = true; - - // During shutdown we lock until we delete the application - SRWExclusiveLock lock(m_srwLock); for (auto & [str, applicationInfo] : m_pApplicationInfoHash) { applicationInfo->ShutDownApplication(/* fServerInitiated */ true); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.h index 2f9f8b84ce5c..efc466dc7ca9 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationmanager.h @@ -47,6 +47,16 @@ class APPLICATION_MANAGER return !m_handlerResolver.GetDisallowRotationOnConfigChange(); } + std::chrono::milliseconds GetShutdownDelay() const + { + return m_handlerResolver.GetShutdownDelay(); + } + + bool UseLegacyShutdown() const + { + return m_handlerResolver.GetShutdownDelay() == std::chrono::milliseconds::zero(); + } + private: std::unordered_map> m_pApplicationInfoHash; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp index 1fde8723bd77..4d55e36b80d9 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp @@ -125,13 +125,14 @@ HRESULT moduleFactory.release(), RQ_EXECUTE_REQUEST_HANDLER, 0)); -; + auto pGlobalModule = std::make_unique(std::move(applicationManager)); RETURN_IF_FAILED(pModuleInfo->SetGlobalNotifications( - pGlobalModule.release(), - GL_CONFIGURATION_CHANGE | // Configuration change triggers IIS application stop - GL_STOP_LISTENING)); // worker process stop or recycle + pGlobalModule.release(), + GL_CONFIGURATION_CHANGE | // Configuration change triggers IIS application stop + GL_STOP_LISTENING | // worker process will stop listening for http requests + GL_APPLICATION_STOP)); // app pool recycle or stop return S_OK; } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp index 94668ed8a34e..9e69d586cb80 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp @@ -6,7 +6,7 @@ extern BOOL g_fInShutdown; ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE(std::shared_ptr pApplicationManager) noexcept - :m_pApplicationManager(std::move(pApplicationManager)) + : m_pApplicationManager(std::move(pApplicationManager)) { } @@ -16,26 +16,52 @@ ASPNET_CORE_GLOBAL_MODULE::ASPNET_CORE_GLOBAL_MODULE(std::shared_ptrShutDown(); - m_pApplicationManager = nullptr; + StartShutdown(); // Return processing to the pipeline. return GL_NOTIFICATION_CONTINUE; } +GLOBAL_NOTIFICATION_STATUS +ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop( + IN IHttpApplicationStopProvider* pProvider +) +{ + UNREFERENCED_PARAMETER(pProvider); + + // If we're already cleaned up just return. + // If user has opted out of the new shutdown behavior ignore this call as we never registered for it before + if (!m_pApplicationManager || m_pApplicationManager->UseLegacyShutdown()) + { + return GL_NOTIFICATION_CONTINUE; + } + + LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalApplicationStop"); + + if (!g_fInShutdown && !m_shutdown.joinable()) + { + // Apps with preload + always running that don't receive a request before recycle/shutdown will never call OnGlobalStopListening + // IISExpress can also close without calling OnGlobalStopListening which is where we usually would trigger shutdown + // so we should make sure to shutdown the server in those cases + StartShutdown(); + } + + return GL_NOTIFICATION_CONTINUE; +} + // // Is called when configuration changed // Recycled the corresponding core app if its configuration changed diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.h b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.h index 80f047e08d74..3bcb30c0b2e8 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/globalmodule.h @@ -4,6 +4,9 @@ #pragma once #include "applicationmanager.h" +#include + +extern BOOL g_fInShutdown; class ASPNET_CORE_GLOBAL_MODULE : NonCopyable, public CGlobalModule { @@ -19,6 +22,12 @@ class ASPNET_CORE_GLOBAL_MODULE : NonCopyable, public CGlobalModule VOID Terminate() override { LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::Terminate"); + + if (m_shutdown.joinable()) + { + m_shutdown.join(); + } + // Remove the class from memory. delete this; } @@ -33,6 +42,48 @@ class ASPNET_CORE_GLOBAL_MODULE : NonCopyable, public CGlobalModule _In_ IGlobalConfigurationChangeProvider * pProvider ) override; + GLOBAL_NOTIFICATION_STATUS + OnGlobalApplicationStop( + IN IHttpApplicationStopProvider* pProvider + ) override; + private: std::shared_ptr m_pApplicationManager; + std::thread m_shutdown; + + void StartShutdown() + { + // Shutdown has already been started/finished + if (m_shutdown.joinable() || g_fInShutdown) + { + return; + } + + // If delay is zero we can go back to the old behavior of calling shutdown inline + // this is primarily so that we have a way for users to revert the new behavior if there are issues with it + if (m_pApplicationManager->UseLegacyShutdown()) + { + LOG_INFO(L"Shutdown starting."); + m_pApplicationManager->ShutDown(); + m_pApplicationManager = nullptr; + } + else + { + // Run shutdown on a background thread. It seems like IIS keeps giving us requests if OnGlobalStopListening is still running + // which will result in 503s from applicationmanager since we're shutting down and don't want to process new requests. + // But if we return ASAP from OnGlobalStopListening, by not shutting down inline and with a small delay to reduce races, + // IIS will actually stop giving us new requests and queue them instead for processing by the new app process. + m_shutdown = std::thread([this]() + { + auto delay = m_pApplicationManager->GetShutdownDelay(); + LOG_INFOF(L"Shutdown starting in %d ms.", delay.count()); + // Delay so that any incoming requests while we're returning from OnGlobalStopListening are allowed to be processed + std::this_thread::sleep_for(delay); + + LOG_INFO(L"Shutdown starting."); + m_pApplicationManager->ShutDown(); + m_pApplicationManager = nullptr; + }); + } + } }; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp index 162c0fea907b..99dd210b3dd1 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/proxymodule.cpp @@ -93,6 +93,7 @@ ASPNET_CORE_PROXY_MODULE::OnExecuteRequestHandler( { if (g_fInShutdown) { + LOG_WARN(L"Received a request during shutdown. Will return a 503 response."); FINISHED(HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS)); } diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp index b876d6dc2656..5282792f1e6f 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.cpp @@ -17,38 +17,40 @@ InProcessApplicationBase::StopInternal(bool fServerInitiated) { AppOfflineTrackingApplication::StopInternal(fServerInitiated); - // Stop was initiated by server no need to do anything, server would stop on it's own - if (fServerInitiated) + // Ignore fServerInitiated for IISExpress + // Recycle doesn't do anything in IISExpress, we need to explicitly shutdown + if (m_pHttpServer.IsCommandLineLaunch()) { + // Send WM_QUIT to the main window to initiate graceful shutdown + EnumWindows([](HWND hwnd, LPARAM) -> BOOL + { + DWORD processId; + + if (GetWindowThreadProcessId(hwnd, &processId) && + processId == GetCurrentProcessId() && + GetConsoleWindow() != hwnd) + { + PostMessage(hwnd, WM_QUIT, 0, 0); + return false; + } + + return true; + }, 0); + return; } - if (!m_pHttpServer.IsCommandLineLaunch()) + // Stop was initiated by server no need to do anything, server would stop on its own + if (fServerInitiated) { - // IIS scenario. - // We don't actually handle any shutdown logic here. - // Instead, we notify IIS that the process needs to be recycled, which will call - // ApplicationManager->Shutdown(). This will call shutdown on the application. - LOG_INFO(L"AspNetCore InProcess Recycle Process on Demand"); - m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); + return; } - else - { - // Send WM_QUIT to the main window to initiate graceful shutdown - EnumWindows([](HWND hwnd, LPARAM) -> BOOL - { - DWORD processId; - if (GetWindowThreadProcessId(hwnd, &processId) && - processId == GetCurrentProcessId() && - GetConsoleWindow() != hwnd) - { - PostMessage(hwnd, WM_QUIT, 0, 0); - return false; - } - - return true; - }, 0); - } + // IIS scenario. + // We don't actually handle any shutdown logic here. + // Instead, we notify IIS that the process needs to be recycled, which will call + // ApplicationManager->Shutdown(). This will call shutdown on the application. + LOG_INFO(L"AspNetCore InProcess Recycle Process on Demand"); + m_pHttpServer.RecycleProcess(L"AspNetCore InProcess Recycle Process on Demand"); } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs index 857c87721bb1..9e08448d969b 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/EventLogHelpers.cs @@ -79,7 +79,7 @@ private static string FormatEntries(IEnumerable entries) return string.Join(",", entries.Select(e => e.Message)); } - private static IEnumerable GetEntries(IISDeploymentResult deploymentResult) + internal static IEnumerable GetEntries(IISDeploymentResult deploymentResult) { var eventLog = new EventLog("Application"); @@ -162,9 +162,16 @@ public static string InProcessFailedToStart(IISDeploymentResult deploymentResult } } - public static string InProcessShutdown() + public static string ShutdownMessage(IISDeploymentResult deploymentResult) { - return "Application 'MACHINE/WEBROOT/APPHOST/.*?' has shutdown."; + if (deploymentResult.DeploymentParameters.HostingModel == HostingModel.InProcess) + { + return "Application 'MACHINE/WEBROOT/APPHOST/.*?' has shutdown."; + } + else + { + return "Application '/LM/W3SVC/1/ROOT' with physical root '.*?' shut down process with Id '.*?' listening on port '.*?'"; + } } public static string ShutdownFileChange(IISDeploymentResult deploymentResult) diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/Helpers.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/Helpers.cs index 0b8c5bbeff58..74f499ce488e 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/Helpers.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Infrastructure/Helpers.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.Extensions.Logging; +using Microsoft.Web.Administration; using Newtonsoft.Json; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests; @@ -179,6 +180,15 @@ public static async Task AssertRecycledAsync(this IISDeploymentResult deployment } } + // Don't use with IISExpress, recycle isn't a valid operation + public static void Recycle(string appPoolName) + { + using var serverManager = new ServerManager(); + var appPool = serverManager.ApplicationPools.FirstOrDefault(ap => ap.Name == appPoolName); + Assert.NotNull(appPool); + appPool.Recycle(); + } + public static IEnumerable ToTheoryData(this Dictionary dictionary) { return dictionary.Keys.Select(k => new[] { k }); diff --git a/src/Servers/IIS/IIS/test/Common.LongTests/ShutdownTests.cs b/src/Servers/IIS/IIS/test/Common.LongTests/ShutdownTests.cs index ac4566d9fda8..4bdc06d5e060 100644 --- a/src/Servers/IIS/IIS/test/Common.LongTests/ShutdownTests.cs +++ b/src/Servers/IIS/IIS/test/Common.LongTests/ShutdownTests.cs @@ -256,7 +256,85 @@ await statusConnection.Receive("5", // Shutdown should be graceful here! EventLogHelpers.VerifyEventLogEvent(deploymentResult, - EventLogHelpers.InProcessShutdown(), Logger); + EventLogHelpers.ShutdownMessage(deploymentResult), Logger); + } + + [ConditionalFact] + [RequiresNewShim] + public async Task RequestsWhileRestartingAppFromConfigChangeAreProcessed() + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + // IISExpress doesn't support recycle + return; + } + + deploymentParameters.HandlerSettings["shutdownDelay"] = "1000"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var result = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + result.Dispose(); + + // Just "touching" web.config should be enough to restart the process + deploymentResult.ModifyWebConfig(element => { }); + + // Default shutdown delay is 1 second, we want to send requests while the shutdown is happening + // So we send a bunch of requests and one of them hopefully will run during shutdown and be queued for processing by the new app + for (var i = 0; i < 2000; i++) + { + using var res = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + await Task.Delay(1); + Assert.Equal(HttpStatusCode.OK, res.StatusCode); + } + + await deploymentResult.AssertRecycledAsync(); + + // Shutdown should be graceful here! + EventLogHelpers.VerifyEventLogEvent(deploymentResult, + EventLogHelpers.ShutdownMessage(deploymentResult), Logger); + } + + [ConditionalFact] + [RequiresNewShim] + public async Task RequestsWhileRecyclingAppAreProcessed() + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(Fixture.InProcessTestSite); + + if (deploymentParameters.ServerType == ServerType.IISExpress) + { + // IISExpress doesn't support recycle + return; + } + + deploymentParameters.HandlerSettings["shutdownDelay"] = "1000"; + + var deploymentResult = await DeployAsync(deploymentParameters); + + var result = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + result.Dispose(); + + // Recycle app pool + Helpers.Recycle(deploymentResult.AppPoolName); + + // Default shutdown delay is 1 second, we want to send requests while the shutdown is happening + // So we send a bunch of requests and one of them hopefully will run during shutdown and be queued for processing by the new app + for (var i = 0; i < 2000; i++) + { + using var res = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + await Task.Delay(1); + Assert.Equal(HttpStatusCode.OK, res.StatusCode); + } + + await deploymentResult.AssertRecycledAsync(); + + // Shutdown should be graceful here! + EventLogHelpers.VerifyEventLogEvent(deploymentResult, + EventLogHelpers.ShutdownMessage(deploymentResult), Logger); } [ConditionalFact] diff --git a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/ApplicationInitializationTests.cs b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/ApplicationInitializationTests.cs index 97589a326e67..2d6626b7b225 100644 --- a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/ApplicationInitializationTests.cs +++ b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/ApplicationInitializationTests.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.ServiceProcess; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -37,9 +38,11 @@ public ApplicationInitializationTests(PublishedSitesFixture fixture) : base(fixt [ConditionalTheory] [RequiresIIS(IISCapability.ApplicationInitialization)] - [InlineData(HostingModel.InProcess)] - [InlineData(HostingModel.OutOfProcess)] - public async Task ApplicationPreloadStartsApp(HostingModel hostingModel) + [InlineData(HostingModel.InProcess, true)] + [InlineData(HostingModel.OutOfProcess, true)] + [InlineData(HostingModel.InProcess, false)] + [InlineData(HostingModel.OutOfProcess, false)] + public async Task ApplicationPreloadStartsApp(HostingModel hostingModel, bool delayShutdown) { // This test often hits a memory leak in warmup.dll module, it has been reported to IIS team using (AppVerifier.Disable(DeployerSelector.ServerType, 0x900)) @@ -49,11 +52,26 @@ public async Task ApplicationPreloadStartsApp(HostingModel hostingModel) (args, contentRoot) => $"{args} CreateFile \"{Path.Combine(contentRoot, "Started.txt")}\""); EnablePreload(baseDeploymentParameters); + baseDeploymentParameters.HandlerSettings["shutdownDelay"] = delayShutdown ? "1000" : "0"; var result = await DeployAsync(baseDeploymentParameters); await Helpers.Retry(async () => await File.ReadAllTextAsync(Path.Combine(result.ContentRoot, "Started.txt")), TimeoutExtensions.DefaultTimeoutValue); StopServer(); EventLogHelpers.VerifyEventLogEvent(result, EventLogHelpers.Started(result), Logger); + + if (delayShutdown) + { + EventLogHelpers.VerifyEventLogEvent(result, EventLogHelpers.ShutdownMessage(result), Logger); + } + else + { + Assert.True(result.HostProcess.HasExited); + + var entries = EventLogHelpers.GetEntries(result); + var expectedRegex = new Regex(EventLogHelpers.ShutdownMessage(result), RegexOptions.Singleline); + var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray(); + Assert.Empty(matchedEntries); + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index e0810f4ffd62..bb42d0e18e6d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -63,6 +63,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived; public bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted; + private bool IsAbortedRead => (_completionState & StreamCompletionFlags.AbortedRead) == StreamCompletionFlags.AbortedRead; public bool IsCompleted => (_completionState & StreamCompletionFlags.Completed) == StreamCompletionFlags.Completed; public Pipe RequestBodyPipe { get; private set; } = default!; @@ -892,12 +893,20 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence payload) InputRemaining -= payload.Length; } - foreach (var segment in payload) + lock (_completionLock) { - RequestBodyPipe.Writer.Write(segment.Span); - } + if (IsAborted || IsAbortedRead) + { + return Task.CompletedTask; + } - return RequestBodyPipe.Writer.FlushAsync().GetAsTask(); + foreach (var segment in payload) + { + RequestBodyPipe.Writer.Write(segment.Span); + } + + return RequestBodyPipe.Writer.FlushAsync().GetAsTask(); + } } protected override void OnReset() diff --git a/src/Shared/Metrics/MetricsExtensions.cs b/src/Shared/Metrics/MetricsExtensions.cs new file mode 100644 index 000000000000..307bb9517601 --- /dev/null +++ b/src/Shared/Metrics/MetricsExtensions.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Http; + +internal static class MetricsExtensions +{ + public static bool TryAddTag(this IHttpMetricsTagsFeature feature, string name, object? value) + { + var tags = feature.Tags; + + // Tags is internally represented as a List. + // Prefer looping through the list to avoid allocating an enumerator. + if (tags is List> list) + { + foreach (var tag in list) + { + if (tag.Key == name) + { + return false; + } + } + } + else + { + foreach (var tag in tags) + { + if (tag.Key == name) + { + return false; + } + } + } + + tags.Add(new KeyValuePair(name, value)); + return true; + } + + public static bool TryAddTag(this ref TagList tags, string name, object? value) + { + for (var i = 0; i < tags.Count; i++) + { + if (tags[i].Key == name) + { + return false; + } + } + + tags.Add(new KeyValuePair(name, value)); + return true; + } +} diff --git a/src/SignalR/clients/ts/signalr/package.json b/src/SignalR/clients/ts/signalr/package.json index db9d0bd3cade..3a4f43382c3a 100644 --- a/src/SignalR/clients/ts/signalr/package.json +++ b/src/SignalR/clients/ts/signalr/package.json @@ -59,14 +59,5 @@ }, "resolutions": { "ansi-regex": "5.0.1" - }, - "browser": { - "./src/DynamicImports.ts": "./src/DynamicImports.browser.ts", - "abort-controller": false, - "eventsource": false, - "fetch-cookie": false, - "node-fetch": false, - "ws": false, - "tough-cookie": false } } diff --git a/src/SignalR/clients/ts/signalr/src/DynamicImports.browser.ts b/src/SignalR/clients/ts/signalr/src/DynamicImports.browser.ts deleted file mode 100644 index 376783f4a4d6..000000000000 --- a/src/SignalR/clients/ts/signalr/src/DynamicImports.browser.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/** @private */ -export function configureFetch(): boolean { - return false; -} - -/** @private */ -export function configureAbortController(): boolean { - return false; -} - -/** @private */ -export function getWS(): any { - throw new Error("Trying to import 'ws' in the browser."); -} - -/** @private */ -export function getEventSource(): any { - throw new Error("Trying to import 'eventsource' in the browser."); -} \ No newline at end of file diff --git a/src/SignalR/clients/ts/signalr/src/DynamicImports.ts b/src/SignalR/clients/ts/signalr/src/DynamicImports.ts deleted file mode 100644 index 8ae470578a54..000000000000 --- a/src/SignalR/clients/ts/signalr/src/DynamicImports.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// @ts-ignore: This will be removed from built files and is here to make the types available during dev work -import { CookieJar } from "@types/tough-cookie"; -import { Platform } from "./Utils"; - -/** @private */ -export function configureFetch(obj: { _fetchType?: (input: RequestInfo, init?: RequestInit) => Promise, - _jar?: CookieJar }): boolean -{ - // Node added a fetch implementation to the global scope starting in v18. - // We need to add a cookie jar in node to be able to share cookies with WebSocket - if (typeof fetch === "undefined" || Platform.isNode) { - // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests - // eslint-disable-next-line @typescript-eslint/no-var-requires - obj._jar = new (require("tough-cookie")).CookieJar(); - - if (typeof fetch === "undefined") { - // eslint-disable-next-line @typescript-eslint/no-var-requires - obj._fetchType = require("node-fetch"); - } else { - // Use fetch from Node if available - obj._fetchType = fetch; - } - - // node-fetch doesn't have a nice API for getting and setting cookies - // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one - // eslint-disable-next-line @typescript-eslint/no-var-requires - obj._fetchType = require("fetch-cookie")(obj._fetchType, obj._jar); - return true; - } - return false; -} - -/** @private */ -export function configureAbortController(obj: { _abortControllerType: { prototype: AbortController, new(): AbortController } }): boolean { - if (typeof AbortController === "undefined") { - // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide - obj._abortControllerType = require("abort-controller"); - return true; - } - return false; -} - -/** @private */ -export function getWS(): any { - return require("ws"); -} - -/** @private */ -export function getEventSource(): any { - return require("eventsource"); -} \ No newline at end of file diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts index 9b26e98c3a78..d6a94fc10239 100644 --- a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -8,7 +8,6 @@ import { AbortError, HttpError, TimeoutError } from "./Errors"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { Platform, getGlobalThis, isArrayBuffer } from "./Utils"; -import { configureAbortController, configureFetch } from "./DynamicImports"; export class FetchHttpClient extends HttpClient { private readonly _abortControllerType: { prototype: AbortController, new(): AbortController }; @@ -21,19 +20,38 @@ export class FetchHttpClient extends HttpClient { super(); this._logger = logger; - // This is how you do "reference" arguments - const fetchObj = { _fetchType: undefined, _jar: undefined }; - if (configureFetch(fetchObj)) { - this._fetchType = fetchObj._fetchType!; - this._jar = fetchObj._jar; + // Node added a fetch implementation to the global scope starting in v18. + // We need to add a cookie jar in node to be able to share cookies with WebSocket + if (typeof fetch === "undefined" || Platform.isNode) { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + + // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests + this._jar = new (requireFunc("tough-cookie")).CookieJar(); + + if (typeof fetch === "undefined") { + this._fetchType = requireFunc("node-fetch"); + } else { + // Use fetch from Node if available + this._fetchType = fetch; + } + + // node-fetch doesn't have a nice API for getting and setting cookies + // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one + this._fetchType = requireFunc("fetch-cookie")(this._fetchType, this._jar); } else { this._fetchType = fetch.bind(getGlobalThis()); } + if (typeof AbortController === "undefined") { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; - this._abortControllerType = AbortController; - const abortObj = { _abortControllerType: this._abortControllerType }; - if (configureAbortController(abortObj)) { - this._abortControllerType = abortObj._abortControllerType; + // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide + this._abortControllerType = requireFunc("abort-controller"); + } else { + this._abortControllerType = AbortController; } } diff --git a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts index 3e48a83d61f4..f8397f4d65b5 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts @@ -3,7 +3,6 @@ import { AccessTokenHttpClient } from "./AccessTokenHttpClient"; import { DefaultHttpClient } from "./DefaultHttpClient"; -import { getEventSource, getWS } from "./DynamicImports"; import { AggregateErrors, DisabledTransportError, FailedToNegotiateWithServerError, FailedToStartTransportError, HttpError, UnsupportedTransportError, AbortError } from "./Errors"; import { IConnection } from "./IConnection"; import { IHttpConnectionOptions } from "./IHttpConnectionOptions"; @@ -88,8 +87,11 @@ export class HttpConnection implements IConnection { let eventSourceModule: any = null; if (Platform.isNode && typeof require !== "undefined") { - webSocketModule = getWS(); - eventSourceModule = getEventSource(); + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + webSocketModule = requireFunc("ws"); + eventSourceModule = requireFunc("eventsource"); } if (!Platform.isNode && typeof WebSocket !== "undefined" && !options.WebSocket) { diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts index 3612759b72c9..4ea7a78acaaa 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts @@ -614,8 +614,10 @@ export class HubConnection { switch (message.type) { case MessageType.Invocation: - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._invokeClientMethod(message); + this._invokeClientMethod(message) + .catch((e) => { + this._logger.log(LogLevel.Error, `Invoke client method threw error: ${getErrorString(e)}`) + }); break; case MessageType.StreamItem: case MessageType.Completion: { diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts index f382a805fc00..f01d68687421 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts @@ -818,6 +818,38 @@ describe("HubConnection", () => { }); }); + it("callback invoked when server invokes a method on the client and then handles rejected promise on send", async () => { + await VerifyLogger.run(async (logger) => { + const connection = new TestConnection(); + const hubConnection = createHubConnection(connection, logger); + let promiseRejected = false; + try { + await hubConnection.start(); + const p = new PromiseSource(); + hubConnection.on("message", async () => { + // Force sending of response to error + connection.send = () => { + promiseRejected = true; + return Promise.reject(new Error("Send error")); + } + p.resolve(); + }); + connection.receive({ + arguments: ["test"], + nonblocking: true, + target: "message", + invocationId: "0", + type: MessageType.Invocation, + }); + + await p; + expect(promiseRejected).toBe(true); + } finally { + await hubConnection.stop(); + } + }, new RegExp("Invoke client method threw error: Error: Send error")); + }); + it("stop on handshake error", async () => { await VerifyLogger.run(async (logger) => { const connection = new TestConnection(false); diff --git a/src/Tools/Microsoft.dotnet-openapi/src/Microsoft.dotnet-openapi.csproj b/src/Tools/Microsoft.dotnet-openapi/src/Microsoft.dotnet-openapi.csproj index 660d406692e2..3dda9082146e 100644 --- a/src/Tools/Microsoft.dotnet-openapi/src/Microsoft.dotnet-openapi.csproj +++ b/src/Tools/Microsoft.dotnet-openapi/src/Microsoft.dotnet-openapi.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj b/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj index 91c5ec3c5fe4..d753a475d044 100644 --- a/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj +++ b/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj index 119c210c6eae..d32c05dcb54a 100644 --- a/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj +++ b/src/Tools/dotnet-user-jwts/src/dotnet-user-jwts.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj index 01716ab85f0b..1a60f3aa072d 100644 --- a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj +++ b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj @@ -29,6 +29,7 @@ + diff --git a/src/submodules/googletest b/src/submodules/googletest index 77afe8e0149c..a7f443b80b10 160000 --- a/src/submodules/googletest +++ b/src/submodules/googletest @@ -1 +1 @@ -Subproject commit 77afe8e0149c207edd9561c28de6d2226673b51f +Subproject commit a7f443b80b105f940225332ed3c31f2790092f47