diff --git a/NuGet.config b/NuGet.config index f5d696249a6..98832105032 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 48e5aaccd3d..ae7b9a12887 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,9 +6,9 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26466.121 - 10.0.0-beta.26466.121 - 10.0.0-beta.26466.121 + 10.0.0-beta.26469.106 + 10.0.0-beta.26469.106 + 10.0.0-beta.26469.106 10.0.13 10.0.13 10.0.13 @@ -16,10 +16,10 @@ This file should be imported by eng/Versions.props 10.0.13 10.0.13 10.0.13 - 10.0.13-servicing.26466.121 + 10.0.13-servicing.26469.106 10.0.13 10.0.13 - 10.0.13-servicing.26466.121 + 10.0.13-servicing.26469.106 10.0.13 10.0.13 10.0.13 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 26af9734cf0..f5aaacdb574 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,80 +1,80 @@ - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea - + https://github.com/dotnet/dotnet - 8e67f200553662d0a05cfa62db77f020b7993937 + 2e99fe9a8c6882728b3d23c956d46faaca861cea diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml index 53bbf74927e..1e685f59331 100644 --- a/eng/common/core-templates/job/helix-job-monitor.yml +++ b/eng/common/core-templates/job/helix-job-monitor.yml @@ -52,11 +52,29 @@ parameters: type: string default: https://helix.dot.net/ -# Helix API access token forwarded to the tool via the HELIX_ACCESSTOKEN environment variable. +# Helix API access token forwarded via HELIX_ACCESSTOKEN. Not forwarded when +# useEntraAuthentication is true. - name: helixAccessToken type: string default: '' +# Use a refreshable Entra credential instead of a PAT or anonymous access. +- name: useEntraAuthentication + type: boolean + default: false + +# Explicit Entra scope for a custom Helix API host (--helix-entra-scope). +# Production and staging scopes are inferred when omitted. +- name: helixEntraScope + type: string + default: '' + +# Azure service connection ID authorized for Helix. Required when +# useEntraAuthentication is true. +- name: azureSubscription + type: string + default: '' + # Polling interval in seconds (--polling-interval-seconds). - name: pollingIntervalSeconds type: number @@ -141,6 +159,26 @@ jobs: - checkout: self fetchDepth: 1 + - ${{ if and(eq(parameters.useEntraAuthentication, true), eq(parameters.azureSubscription, '')) }}: + - pwsh: throw "azureSubscription must be set when useEntraAuthentication is true." + displayName: Validate Helix Entra authentication + + - ${{ if eq(parameters.useEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Initialize Helix Entra authentication + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } + + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + - ${{ if ne(parameters.toolNupkgArtifactName, '') }}: - task: DownloadPipelineArtifact@2 displayName: Download Helix Job Monitor artifact @@ -214,6 +252,8 @@ jobs: toolArgs=( --helix-base-uri '${{ parameters.helixBaseUri }}' + --use-entra-authentication '${{ parameters.useEntraAuthentication }}' + --helix-entra-scope '${{ parameters.helixEntraScope }}' --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}' --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}' --allow-no-helix-jobs '${{ parameters.allowNoHelixJobs }}' @@ -275,4 +315,9 @@ jobs: displayName: Monitor Helix Jobs env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) - HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} + ${{ if eq(parameters.useEntraAuthentication, false) }}: + HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} + ${{ if eq(parameters.useEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.azureSubscription }} diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index eaed6d87e65..a64f1c496a7 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -109,7 +109,8 @@ jobs: - name: ${{ pair.key }} value: ${{ pair.value }} - # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds. + # Entra-enabled Helix templates do not forward this value to their processes. - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: DotNet-HelixApi-Access diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml index 37678b0389b..3951e47104a 100644 --- a/eng/common/core-templates/steps/send-to-helix.yml +++ b/eng/common/core-templates/steps/send-to-helix.yml @@ -4,7 +4,10 @@ parameters: HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues - HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixAccessToken: '' # optional -- legacy access token; not forwarded when HelixUseEntraAuthentication is true + HelixUseEntraAuthentication: false # optional -- use refreshable Entra authentication instead of a PAT or anonymous access + HelixEntraScope: '' # optional -- explicit Entra scope required for custom HelixBaseUri hosts + HelixAzureSubscription: '' # required when HelixUseEntraAuthentication is true -- Azure service connection ID authorized for Helix HelixProjectPath: 'eng/common/helixpublish.proj' # optional -- path to the project file to build relative to BUILD_SOURCESDIRECTORY HelixProjectArguments: '' # optional -- arguments passed to the build command HelixConfiguration: '' # optional -- additional property attached to a job @@ -32,12 +35,35 @@ parameters: continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false steps: + - ${{ if and(eq(parameters.HelixUseEntraAuthentication, true), eq(parameters.HelixAzureSubscription, '')) }}: + - pwsh: throw "HelixAzureSubscription must be set when HelixUseEntraAuthentication is true." + displayName: Validate Helix Entra authentication + condition: ${{ parameters.condition }} + + - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Initialize Helix Entra authentication + inputs: + azureSubscription: ${{ parameters.HelixAzureSubscription }} + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } + + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + condition: ${{ parameters.condition }} + - powershell: > $(Build.SourcesDirectory)\eng\common\msbuild.ps1 $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ${{ parameters.HelixProjectArguments }} /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog @@ -49,7 +75,13 @@ steps: HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} + HelixEntraScope: ${{ parameters.HelixEntraScope }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} @@ -76,6 +108,7 @@ steps: /restore /p:TreatWarningsAsErrors=false /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ${{ parameters.HelixProjectArguments }} /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog @@ -87,7 +120,13 @@ steps: HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} + HelixEntraScope: ${{ parameters.HelixEntraScope }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} diff --git a/global.json b/global.json index 9e134c55a45..4ee694243c4 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26466.121", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26466.121" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26469.106", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26469.106" } } diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs index 8d568b87e2d..19e8d5b2d53 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs @@ -160,8 +160,9 @@ public void DoNotPool() public void Activate(SqliteConnection outerConnection) { - _active = true; + // Publish the owner before making this connection eligible for leak reclamation. _outerConnection.SetTarget(outerConnection); + _active = true; } public void Close() diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionFactoryTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionFactoryTest.cs index 176e6e4c685..42bdbf4dc75 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionFactoryTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionFactoryTest.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Data; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using SQLitePCL; using Xunit; @@ -18,6 +20,95 @@ public class SqliteConnectionFactoryTest : IDisposable private const string FileName = "pooled.db"; private const string ConnectionString = "Data Source=" + FileName + ";Cache=Shared;Pooling=True"; + [Fact] + public async Task Concurrent_opens_do_not_share_internal_connections() + { + const int workerCount = 16; + const int iterations = 1000; + var connections = new SqliteConnection?[workerCount]; + var errors = new ConcurrentQueue(); + var duplicate = false; + using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + using var barrier = new Barrier(workerCount + 1); + using var pool = new SqliteConnection(ConnectionString); + + // Dedicated workers avoid blocking thread-pool threads at the phase barriers. + var workers = Enumerable.Range(0, workerCount).Select(index => Task.Factory.StartNew( + () => + { + for (var iteration = 0; iteration < iterations; iteration++) + { + barrier.SignalAndWait(timeout.Token); + try + { + connections[index] = new SqliteConnection(ConnectionString); + connections[index]!.Open(); + } + catch (Exception exception) + { + errors.Enqueue(exception); + } + + try + { + barrier.SignalAndWait(timeout.Token); + // Keep every owner alive and open until its handle has been inspected. + barrier.SignalAndWait(timeout.Token); + } + finally + { + try + { + connections[index]?.Dispose(); + } + catch (Exception exception) + { + errors.Enqueue(exception); + } + } + + barrier.SignalAndWait(timeout.Token); + if (duplicate || !errors.IsEmpty) + { + break; + } + } + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray(); + + var completed = false; + try + { + for (var iteration = 0; iteration < iterations; iteration++) + { + // Only clear this pool, between waves while all previous owners are closed. + SqliteConnection.ClearPool(pool); + barrier.SignalAndWait(timeout.Token); + barrier.SignalAndWait(timeout.Token); + duplicate = connections.Select(c => c?.Handle).Distinct().Count() != workerCount; + barrier.SignalAndWait(timeout.Token); + barrier.SignalAndWait(timeout.Token); + if (duplicate || !errors.IsEmpty) + { + break; + } + } + + completed = true; + } + finally + { + if (!completed) + { + timeout.Cancel(); + } + + await Task.WhenAll(workers); + } + + Assert.Empty(errors); + Assert.False(duplicate); + } + [Fact] public void Internal_connections_are_reused_after_reopen() {