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()
{