diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2a953e7df0..57dfb02004 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -170,13 +170,13 @@ - + https://github.com/dotnet/arcade - 67d23f4ba1813b315e7e33c71d18b63475f5c5f8 + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac - + https://github.com/dotnet/arcade - 67d23f4ba1813b315e7e33c71d18b63475f5c5f8 + e6f70c7dd528f05cd28cec2a179d58c22e91d9ac diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml index f0513aee5b..43ee0c202f 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 b98202aa02..43ee0c202f 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/packages/General.props b/eng/packages/General.props index c67317652c..c02cfb54dd 100644 --- a/eng/packages/General.props +++ b/eng/packages/General.props @@ -51,7 +51,6 @@ - diff --git a/global.json b/global.json index f6d5259566..7c55ca32aa 100644 --- a/global.json +++ b/global.json @@ -16,7 +16,7 @@ "msbuild-sdks": { "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.2.0", - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24225.1", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24225.1" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24266.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24266.3" } } diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj index 053db6d3e6..9ad04ae889 100644 --- a/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj @@ -37,7 +37,6 @@ - diff --git a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/README.md b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/README.md index 7c334ad800..c1dfddbb9f 100644 --- a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/README.md +++ b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/README.md @@ -42,6 +42,102 @@ timeProvider.Advance(TimeSpan.FromSeconds(5)); myComponent.CheckState(); ``` +## Use ConfigureAwait(true) with FakeTimeProvider.Advance + +The Advance method is used to simulate the passage of time. This can be useful in tests where you need to control the timing of asynchronous operations. +When awaiting a task in a test that uses `FakeTimeProvider`, it's important to use `ConfigureAwait(true)`. + +Here's an example: + +```cs +await provider.Delay(TimeSpan.FromSeconds(delay)).ConfigureAwait(true); +``` + +This ensures that the continuation of the awaited task (i.e., the code that comes after the await statement) runs in the original context. + +For a more realistic example, consider the following test using Polly: + +```cs +using Polly; +using Polly.Retry; + +public class SomeService(TimeProvider timeProvider) +{ + // Don't do this in real life, not thread safe + public int Tries { get; private set; } + + private readonly ResiliencePipeline _retryPipeline = new ResiliencePipelineBuilder { TimeProvider = timeProvider } + .AddRetry( + new RetryStrategyOptions + { + ShouldHandle = new PredicateBuilder().Handle(), + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 2, + BackoffType = DelayBackoffType.Linear, + }) + .Build(); + + public async Task PollyRetry(double taskDelay, double cancellationSeconds) + { + CancellationTokenSource cts = new(TimeSpan.FromSeconds(cancellationSeconds), timeProvider); + Tries = 0; + + // get a context from the pool and return it when done + var context = ResilienceContextPool.Shared.Get( + // ensure execution continues on captured context + continueOnCapturedContext: true, + cancellationToken: cts.Token); + + var result = await _retryPipeline.ExecuteAsync( + async _ => + { + Tries++; + + // Simulate a task that takes some time to complete + await Task.Delay(TimeSpan.FromSeconds(taskDelay), timeProvider).ConfigureAwait(true); + + if (Tries <= 2) + { + throw new InvalidOperationException(); + } + + return Tries; + }, + context); + + ResilienceContextPool.Shared.Return(context); + + return result; + } +} + +using Microsoft.Extensions.Time.Testing; + +public class SomeServiceTests +{ + [Fact] + public void PollyRetry_ShouldHave2Tries() + { + var timeProvider = new FakeTimeProvider(); + var someService = new SomeService(timeProvider); + + // Act + var result = someService.PollyRetry(taskDelay: 1, cancellationSeconds: 6); + + // Advancing the time more than one second should resolves the first execution delay. + timeProvider.Advance(TimeSpan.FromMilliseconds(1001)); + + // Advancing the time more than the retry delay time of 1s, + // and less then the task execution delay should start the second try + timeProvider.Advance(TimeSpan.FromMilliseconds(1050)); + + // Assert + result.IsCompleted.Should().BeFalse(); + someService.Tries.Should().Be(2); + } +} +``` + ## Feedback & Contributing We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs index b7c70a715a..0e438cfe4d 100644 --- a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs @@ -381,4 +381,57 @@ public class FakeTimeProviderTests // Assert Assert.Single(calls); } + + [Fact] + public void SimulateRetryPolicy() + { + // Arrange + var retries = 42; + var tries = 0; + var taskDelay = 0.5; + var delay = 1; + var provider = new FakeTimeProvider(); + + async Task simulatedPollyRetry() + { + while (true) + { + try + { + // simulate task that takes some time to complete + await provider.Delay(TimeSpan.FromSeconds(taskDelay)); + tries++; + + if (tries <= retries) + { + // the task failed, trigger retry + throw new InvalidOperationException(); + } + + return tries; + } + catch (InvalidOperationException) + { + // ConfigureAwait(true) is required to ensure that tasks continue on the captured context + await provider.Delay(TimeSpan.FromSeconds(delay)).ConfigureAwait(true); + } + } + } + + // Act + var result = simulatedPollyRetry(); + + for (int i = 0; i < retries; i++) + { + // advance time for simulated task delay + provider.Advance(TimeSpan.FromSeconds(taskDelay)); + + // advance time for retry delay + provider.Advance(TimeSpan.FromSeconds(delay)); + } + + // Assert + Assert.False(result.IsCompleted); + Assert.Equal(retries, tries); + } } diff --git a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/Microsoft.Extensions.TimeProvider.Testing.Tests.csproj b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/Microsoft.Extensions.TimeProvider.Testing.Tests.csproj index d0884c2ac5..142b573ae0 100644 --- a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/Microsoft.Extensions.TimeProvider.Testing.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/Microsoft.Extensions.TimeProvider.Testing.Tests.csproj @@ -5,7 +5,7 @@ - +