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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy