From 31fe2cc2a151121809f90ba3a7d3e4ed9d4516a1 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Sun, 18 Feb 2024 08:05:02 -0800 Subject: [PATCH 1/8] Support .NET 9.0. --- .ci/build-steps.yml | 34 +++++++------- .ci/conformance-test-steps.yml | 6 +-- .ci/integration-tests-steps.yml | 10 ++--- .ci/mysqlconnector-tests-steps.yml | 6 +-- .ci/test.ps1 | 4 +- appveyor.yml | 2 +- azure-pipelines.yml | 45 +++++++------------ global.json | 2 +- src/MySqlConnector/MySqlConnector.csproj | 2 +- .../Conformance.Tests.csproj | 2 +- tests/IntegrationTests/ClientFactoryTests.cs | 2 +- .../IntegrationTests/IntegrationTests.csproj | 6 +-- tests/IntegrationTests/Transaction.cs | 4 +- ...Connector.DependencyInjection.Tests.csproj | 2 +- .../MySqlConnector.Tests.csproj | 2 +- .../SchemaCollectionGenerator.csproj | 2 +- 16 files changed, 58 insertions(+), 73 deletions(-) diff --git a/.ci/build-steps.yml b/.ci/build-steps.yml index e81044196..384c6aed7 100644 --- a/.ci/build-steps.yml +++ b/.ci/build-steps.yml @@ -27,58 +27,58 @@ steps: displayName: 'Publish MySqlConnector.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/MySqlConnector.Tests/release_net8.0' + artifactName: 'MySqlConnector.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/MySqlConnector.Tests/release_net9.0' - task: DotNetCoreCLI@2 displayName: 'Publish Conformance.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/Conformance.Tests/Conformance.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/Conformance.Tests/Conformance.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'Conformance.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/Conformance.Tests/release_net8.0' + artifactName: 'Conformance.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/Conformance.Tests/release_net9.0' - task: DotNetCoreCLI@2 displayName: 'Publish MySqlConnector.DependencyInjection.Tests' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net8.0' + artifactName: 'MySqlConnector.DependencyInjection.Tests-9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net9.0' - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (7.0)' + displayName: 'Publish IntegrationTests (9.0)' inputs: command: 'publish' - arguments: '-c Release -f net8.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' + arguments: '-c Release -f net9.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'IntegrationTests-net8.0-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net8.0' + artifactName: 'IntegrationTests-net9.0-$(Agent.OS)' + targetPath: 'artifacts/publish/IntegrationTests/release_net9.0' - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (6.0)' + displayName: 'Publish IntegrationTests (8.0)' inputs: command: 'publish' - arguments: '-c Release -f net6.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' + arguments: '-c Release -f net8.0 --no-build tests/IntegrationTests/IntegrationTests.csproj' publishWebProjects: false zipAfterPublish: false - task: PublishPipelineArtifact@0 inputs: - artifactName: 'IntegrationTests-net6.0-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net6.0' + artifactName: 'IntegrationTests-net8.0-$(Agent.OS)' + targetPath: 'artifacts/publish/IntegrationTests/release_net8.0' diff --git a/.ci/conformance-test-steps.yml b/.ci/conformance-test-steps.yml index 02d700623..349663215 100644 --- a/.ci/conformance-test-steps.yml +++ b/.ci/conformance-test-steps.yml @@ -10,14 +10,14 @@ steps: - task: DownloadPipelineArtifact@0 condition: always() inputs: - artifactName: 'Conformance.Tests-8.0-$(Agent.OS)' - targetPath: '$(Build.BinariesDirectory)/8.0' + artifactName: 'Conformance.Tests-9.0-$(Agent.OS)' + targetPath: '$(Build.BinariesDirectory)/9.0' - task: DotNetCoreCLI@2 displayName: 'Conformance Tests' inputs: command: 'custom' custom: 'vstest' - arguments: '$(Build.BinariesDirectory)/8.0/Conformance.Tests.dll /logger:trx' + arguments: '$(Build.BinariesDirectory)/9.0/Conformance.Tests.dll /logger:trx' env: CONNECTION_STRING: ${{ parameters.connectionString }} - task: PublishTestResults@2 diff --git a/.ci/integration-tests-steps.yml b/.ci/integration-tests-steps.yml index 14f8a0cc6..7d7bd5d08 100644 --- a/.ci/integration-tests-steps.yml +++ b/.ci/integration-tests-steps.yml @@ -8,9 +8,9 @@ steps: - bash: ${{ format('.ci/docker-run.sh {0} 3300 {1}', parameters.image, parameters.unsupportedFeatures) }} displayName: 'Start Docker container' - task: UseDotNet@2 - displayName: 'Install .NET 6.0' + displayName: 'Install .NET 8.0' inputs: - version: 6.0.x + version: 8.0.x packageType: runtime - task: UseDotNet@2 displayName: 'Install .NET' @@ -33,19 +33,19 @@ steps: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: 'server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;UseCompression=True;DefaultCommandTimeout=3600;${{ parameters.connectionStringExtra }}' - platform: 'net6.0' + platform: 'net8.0' description: 'Compression' - template: 'integration-test-steps.yml' parameters: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: 'server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;${{ parameters.connectionStringExtra }}' - platform: 'net6.0' + platform: 'net9.0' description: 'No SSL' - template: 'integration-test-steps.yml' parameters: image: ${{ parameters.image }} unsupportedFeatures: ${{ parameters.unsupportedFeatures }} connectionString: server=localhost;port=3300;user id=mysqltest;password=test;database=mysqltest;ssl mode=required;DefaultCommandTimeout=3600;certificate file=$(Build.Repository.LocalPath)/.ci/server/certs/ssl-client.pfx;${{ parameters.connectionStringExtra }} - platform: 'net6.0' + platform: 'net9.0' description: 'SSL' diff --git a/.ci/mysqlconnector-tests-steps.yml b/.ci/mysqlconnector-tests-steps.yml index 012b11a0b..ef0e6a095 100644 --- a/.ci/mysqlconnector-tests-steps.yml +++ b/.ci/mysqlconnector-tests-steps.yml @@ -6,7 +6,7 @@ steps: includePreviewVersions: true - task: DownloadPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.Tests-8.0-$(Agent.OS)' + artifactName: 'MySqlConnector.Tests-9.0-$(Agent.OS)' targetPath: $(System.DefaultWorkingDirectory) - task: DotNetCoreCLI@2 displayName: 'Run MySqlConnector.Tests' @@ -16,7 +16,7 @@ steps: arguments: 'MySqlConnector.Tests.dll /logger:trx' - task: DownloadPipelineArtifact@0 inputs: - artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)' + artifactName: 'MySqlConnector.DependencyInjection.Tests-9.0-$(Agent.OS)' targetPath: $(System.DefaultWorkingDirectory) - task: DotNetCoreCLI@2 displayName: 'Run MySqlConnector.DependencyInjection.Tests' @@ -28,4 +28,4 @@ steps: inputs: testResultsFormat: VSTest testResultsFiles: '**/*.trx' - testRunTitle: 'MySqlConnector.Tests-8.0-$(Agent.OS)' + testRunTitle: 'MySqlConnector.Tests-9.0-$(Agent.OS)' diff --git a/.ci/test.ps1 b/.ci/test.ps1 index a619c6402..7d595e35e 100644 --- a/.ci/test.ps1 +++ b/.ci/test.ps1 @@ -38,14 +38,14 @@ dotnet test -c Release -f net462 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } -dotnet test -c Release -f net7.0 +dotnet test -c Release -f net9.0 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } echo "Executing integration tests with Compression, No SSL" Copy-Item -Force ..\..\.ci\config\config.compression.json config.json -dotnet test -c Release -f net6.0 +dotnet test -c Release -f net8.0 if ($LASTEXITCODE -ne 0){ exit $LASTEXITCODE; } diff --git a/appveyor.yml b/appveyor.yml index c4f4f64c4..ef905dcaa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,8 +8,8 @@ cache: install: - ps: Invoke-WebRequest -Uri "https://dot.net/v1/dotnet-install.ps1" -OutFile "install-dotnet.ps1" - ps: .\install-dotnet.ps1 -Channel 6.0 -InstallDir "dotnetcli" - - ps: .\install-dotnet.ps1 -Channel 7.0 -InstallDir "dotnetcli" - ps: .\install-dotnet.ps1 -Channel 8.0 -InstallDir "dotnetcli" + - ps: .\install-dotnet.ps1 -Channel 9.0 -InstallDir "dotnetcli" build_script: - dotnet --info before_test: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c0c83dd5b..7bc876b2c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,5 @@ variables: - DotNetCoreSdkVersion: '8.x' + DotNetCoreSdkVersion: '9.x' NUGET_PACKAGES: '$(Pipeline.Workspace)/.nuget/packages' jobs: @@ -16,17 +16,6 @@ jobs: vmimage: 'windows-latest' steps: - template: '.ci/build-steps.yml' - - task: DotNetCoreCLI@2 - displayName: 'Publish IntegrationTests (net472)' - inputs: - command: 'publish' - arguments: '-c Release -f net472 tests/IntegrationTests/IntegrationTests.csproj' - publishWebProjects: false - zipAfterPublish: false - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'IntegrationTests-net472-$(Agent.OS)' - targetPath: 'artifacts/publish/IntegrationTests/release_net472' - job: windows_mysql_data displayName: 'MySql.Data Tests' @@ -41,9 +30,9 @@ jobs: contents: 'config.json' targetFolder: 'tests/IntegrationTests' - task: UseDotNet@2 - displayName: 'Install .NET 7.0' + displayName: 'Install .NET 8.0' inputs: - version: '7.0.x' + version: '8.0.x' - task: UseDotNet@2 displayName: 'Install .NET' inputs: @@ -91,11 +80,6 @@ jobs: vmimage: 'windows-2019' steps: - template: '.ci/install-mysql-windows.yml' - - task: UseDotNet@2 - displayName: 'Install .NET Core 3.1' - inputs: - version: 3.1.x - packageType: runtime - template: '.ci/mysqlconnector-tests-steps.yml' - template: '.ci/conformance-test-steps.yml' parameters: @@ -117,6 +101,7 @@ jobs: displayName: 'Install .NET' inputs: version: $(DotNetCoreSdkVersion) + includePreviewVersions: true - task: PowerShell@2 displayName: 'Copy Azure config' inputs: @@ -127,12 +112,12 @@ jobs: inputs: command: 'restore' - task: DotNetCoreCLI@2 - displayName: 'Integration tests (net8.0)' + displayName: 'Integration tests (net9.0)' inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' - arguments: '-c Release -f net8.0 --no-restore' - testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'Azure', 'net8.0', 'SSL') }} + arguments: '-c Release -f net9.0 --no-restore' + testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'Azure', 'net9.0', 'SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'CachingSha2Password,Ed25519,GlobalLog,KnownCertificateAuthority,QueryAttributes,RsaEncryption,Sha256Password,StreamingResults,Timeout,Tls11,Tls13,UnixDomainSocket,ZeroDateTime' DATA__CONNECTIONSTRING: "$(AzureConnectionString);database=mysqltest_$(Build.BuildId);ssl mode=Required;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True" @@ -164,18 +149,18 @@ jobs: displayName: 'Remove target frameworks' inputs: targetType: 'inline' - script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net472;net8.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' + script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net481;net9.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' - task: DotNetCoreCLI@2 - displayName: 'Integration tests (net472/net8.0)' + displayName: 'Integration tests (net481/net9.0)' inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' arguments: '-c Release --no-restore' - testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net472/net8.0', 'No SSL') }} + testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net481/net9.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket' DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True;UseCompression=True' @@ -187,9 +172,9 @@ jobs: steps: - template: '.ci/install-mysql-windows.yml' - task: UseDotNet@2 - displayName: 'Install .NET 6.0' + displayName: 'Install .NET 8.0' inputs: - version: 6.0.x + version: 8.0.x packageType: runtime - task: UseDotNet@2 displayName: 'Install .NET' @@ -202,18 +187,18 @@ jobs: displayName: 'Remove target frameworks' inputs: targetType: 'inline' - script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net6.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' + script: '((Get-Content .\tests\IntegrationTests\IntegrationTests.csproj -Raw) -replace(''.*'', ''net8.0'')) | Set-Content .\tests\IntegrationTests\IntegrationTests.csproj' - task: DotNetCoreCLI@2 displayName: 'Restore packages' inputs: command: 'restore' - task: DotNetCoreCLI@2 - displayName: 'Integration tests (net6.0)' + displayName: 'Integration tests (net8.0)' inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' arguments: '-c Release --no-restore' - testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net6.0', 'No SSL') }} + testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net8.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket' DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True' diff --git a/global.json b/global.json index 3a69170ca..746f46e86 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0" + "version": "9.0" } } diff --git a/src/MySqlConnector/MySqlConnector.csproj b/src/MySqlConnector/MySqlConnector.csproj index 4cb3425f5..7e679aeb9 100644 --- a/src/MySqlConnector/MySqlConnector.csproj +++ b/src/MySqlConnector/MySqlConnector.csproj @@ -1,7 +1,7 @@ - net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0 + net462;net471;net48;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0 A truly async MySQL ADO.NET provider, supporting MySQL Server, MariaDB, Amazon Aurora, Azure Database for MySQL, Google Cloud SQL, and more. Copyright 2016–2024 Bradley Grainger Bradley Grainger diff --git a/tests/Conformance.Tests/Conformance.Tests.csproj b/tests/Conformance.Tests/Conformance.Tests.csproj index 246efa5b1..e2a4ca928 100644 --- a/tests/Conformance.Tests/Conformance.Tests.csproj +++ b/tests/Conformance.Tests/Conformance.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 0.1.0 true true diff --git a/tests/IntegrationTests/ClientFactoryTests.cs b/tests/IntegrationTests/ClientFactoryTests.cs index bebd2f5e0..f74017a73 100644 --- a/tests/IntegrationTests/ClientFactoryTests.cs +++ b/tests/IntegrationTests/ClientFactoryTests.cs @@ -51,7 +51,7 @@ public void DbProviderFactoriesGetFactory() #else var providerInvariantName = "MySqlConnector"; #endif -#if !NET462 && !NET472 +#if !NETFRAMEWORK DbProviderFactories.RegisterFactory(providerInvariantName, MySqlConnectorFactory.Instance); #endif var factory = DbProviderFactories.GetFactory(providerInvariantName); diff --git a/tests/IntegrationTests/IntegrationTests.csproj b/tests/IntegrationTests/IntegrationTests.csproj index 8c975155b..7b6a5c1a3 100644 --- a/tests/IntegrationTests/IntegrationTests.csproj +++ b/tests/IntegrationTests/IntegrationTests.csproj @@ -1,12 +1,12 @@ - net462;net472;net6.0;net8.0 + net462;net481;net6.0;net8.0;net9.0 false - net8.0 + net9.0 MYSQL_DATA $(NoWarn);MSB3246 @@ -60,7 +60,7 @@ - + diff --git a/tests/IntegrationTests/Transaction.cs b/tests/IntegrationTests/Transaction.cs index 7a22b96f5..ac33ab5e0 100644 --- a/tests/IntegrationTests/Transaction.cs +++ b/tests/IntegrationTests/Transaction.cs @@ -169,7 +169,7 @@ public async Task ReadWriteTransactionAsync() Assert.Equal(new[] { 1, 2 }, results); } -#if !NET462 && !NET472 +#if !NETFRAMEWORK [Fact] public async Task DbConnectionCommitAsync() { @@ -241,7 +241,7 @@ public async Task RollbackDisposeAsync() Assert.Equal(new int[0], results); } -#if !NET462 && !NET472 +#if !NETFRAMEWORK [Fact] public async Task DbConnectionRollbackAsync() { diff --git a/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj b/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj index fac726a3a..ebeeef845 100644 --- a/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj +++ b/tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 true true ..\..\MySqlConnector.snk diff --git a/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj b/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj index b7c7f164e..5d14a2cce 100644 --- a/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj +++ b/tests/MySqlConnector.Tests/MySqlConnector.Tests.csproj @@ -1,7 +1,7 @@ - net481;net8.0 + net481;net9.0 diff --git a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj index 73e62aa5e..85c1ed799 100644 --- a/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj +++ b/tools/SchemaCollectionGenerator/SchemaCollectionGenerator.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable true From 6ab339c12c83741c6b2bb35e3404a0e71186d99c Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 12 Mar 2024 20:41:14 -0700 Subject: [PATCH 2/8] Use Lock type in .NET 9.0. --- .../Authentication/AuthenticationPlugins.cs | 4 +++ src/MySqlConnector/Core/ILoadBalancer.cs | 26 ++++++++++++++++--- src/MySqlConnector/MySqlBulkLoader.cs | 16 +++++++++--- src/MySqlConnector/MySqlConnection.cs | 22 +++++++++++----- src/MySqlConnector/Utilities/TimerQueue.cs | 4 +++ 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/MySqlConnector/Authentication/AuthenticationPlugins.cs b/src/MySqlConnector/Authentication/AuthenticationPlugins.cs index 025362e0f..92fdf19c7 100644 --- a/src/MySqlConnector/Authentication/AuthenticationPlugins.cs +++ b/src/MySqlConnector/Authentication/AuthenticationPlugins.cs @@ -35,6 +35,10 @@ internal static bool TryGetPlugin(string name, [NotNullWhen(true)] out IAuthenti return s_plugins.TryGetValue(name, out plugin); } +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary s_plugins = []; } diff --git a/src/MySqlConnector/Core/ILoadBalancer.cs b/src/MySqlConnector/Core/ILoadBalancer.cs index d6dd82ebb..568cf5b26 100644 --- a/src/MySqlConnector/Core/ILoadBalancer.cs +++ b/src/MySqlConnector/Core/ILoadBalancer.cs @@ -27,12 +27,18 @@ internal sealed class RandomLoadBalancer : ILoadBalancer public IReadOnlyList LoadBalance(IReadOnlyList hosts) { #pragma warning disable CA5394 // Do not use insecure randomness - // from https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm +#if NET8_0_OR_GREATER + var shuffled = hosts.ToArray(); + lock (m_lock) + m_random.Shuffle(shuffled); + return shuffled; +#else var shuffled = new List(hosts); + // from https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm for (var i = hosts.Count - 1; i >= 1; i--) { int j; - lock (m_random) + lock (m_lock) j = m_random.Next(i + 1); if (i != j) { @@ -42,11 +48,21 @@ public IReadOnlyList LoadBalance(IReadOnlyList hosts) } } return shuffled; +#endif } - private RandomLoadBalancer() => m_random = new(); + private RandomLoadBalancer() + { + m_random = new(); + m_lock = new(); + } private readonly Random m_random; +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else + private readonly object m_lock; +#endif } internal sealed class RoundRobinLoadBalancer : ILoadBalancer @@ -67,6 +83,10 @@ public IReadOnlyList LoadBalance(IReadOnlyList hosts) return shuffled; } +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else private readonly object m_lock; +#endif private uint m_counter; } diff --git a/src/MySqlConnector/MySqlBulkLoader.cs b/src/MySqlConnector/MySqlBulkLoader.cs index 1ab11c8fc..8dcf933f4 100644 --- a/src/MySqlConnector/MySqlBulkLoader.cs +++ b/src/MySqlConnector/MySqlBulkLoader.cs @@ -176,8 +176,7 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken { // replace the file name with a sentinel so that we know (when processing the result set) that it's not spoofed by the server var newFileName = GenerateSourceFileName(); - lock (s_lock) - s_sources.Add(newFileName, CreateFileStream(FileName!)); + AddSource(newFileName, CreateFileStream(FileName!)); FileName = newFileName; } } @@ -187,8 +186,7 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken throw new InvalidOperationException("Local must be true to use SourceStream, SourceDataTable, or SourceDataReader."); FileName = GenerateSourceFileName(); - lock (s_lock) - s_sources.Add(FileName, Source!); + AddSource(FileName, Source!); } var closeConnection = false; @@ -221,6 +219,12 @@ internal async ValueTask LoadAsync(IOBehavior ioBehavior, CancellationToken if (closeConnection) Connection.Close(); } + + static void AddSource(string name, object source) + { + lock (s_lock) + s_sources.Add(name, source); + } } internal const string SourcePrefix = ":SOURCE:"; @@ -319,6 +323,10 @@ internal static bool TryGetAndRemoveSource(string sourceKey, [NotNullWhen(true)] private static string GenerateSourceFileName() => SourcePrefix + Guid.NewGuid().ToString("N"); +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary s_sources = []; } diff --git a/src/MySqlConnector/MySqlConnection.cs b/src/MySqlConnector/MySqlConnection.cs index 5ab8ff9f8..fc327a4a3 100644 --- a/src/MySqlConnector/MySqlConnection.cs +++ b/src/MySqlConnector/MySqlConnection.cs @@ -1189,16 +1189,20 @@ private async Task DoCloseAsync(bool changeState, IOBehavior ioBehavior) }; connection.TakeSessionFrom(this); - // put the new, idle, connection into the list of sessions for this transaction (replacing this MySqlConnection) - lock (s_lock) + ReplaceConnection(this, connection); + static void ReplaceConnection(MySqlConnection thisConnection, MySqlConnection connection) { - foreach (var enlistedTransaction in s_transactionConnections[connection.m_enlistedTransaction!.Transaction]) + // put the new, idle, connection into the list of sessions for this transaction (replacing this MySqlConnection) + lock (s_lock) { - if (enlistedTransaction.Connection == this) + foreach (var enlistedTransaction in s_transactionConnections[connection.m_enlistedTransaction!.Transaction]) { - enlistedTransaction.Connection = connection; - enlistedTransaction.IsIdle = true; - break; + if (enlistedTransaction.Connection == thisConnection) + { + enlistedTransaction.Connection = connection; + enlistedTransaction.IsIdle = true; + break; + } } } } @@ -1256,7 +1260,11 @@ private ConnectionSettings GetConnectionSettings() => private static readonly StateChangeEventArgs s_stateChangeClosedConnecting = new(ConnectionState.Closed, ConnectionState.Connecting); private static readonly StateChangeEventArgs s_stateChangeConnectingOpen = new(ConnectionState.Connecting, ConnectionState.Open); private static readonly StateChangeEventArgs s_stateChangeOpenClosed = new(ConnectionState.Open, ConnectionState.Closed); +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else private static readonly object s_lock = new(); +#endif private static readonly Dictionary> s_transactionConnections = []; private static readonly ReadOnlyMemory[] s_startTransactionPayloads = new ReadOnlyMemory[5 * 3 * 2]; diff --git a/src/MySqlConnector/Utilities/TimerQueue.cs b/src/MySqlConnector/Utilities/TimerQueue.cs index 5aa05f736..3b120264e 100644 --- a/src/MySqlConnector/Utilities/TimerQueue.cs +++ b/src/MySqlConnector/Utilities/TimerQueue.cs @@ -131,7 +131,11 @@ public Data(uint id, int time, Action action) public Action Action { get; } } +#if NET9_0_OR_GREATER + private readonly Lock m_lock; +#else private readonly object m_lock; +#endif private readonly Timer m_timer; private readonly List m_timeoutActions; private uint m_counter; From 31b0ea9f9b466866f2d7c84266f96c7a0b0866e3 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 12 Mar 2024 22:00:55 -0700 Subject: [PATCH 3/8] Run multiple TFM tests sequentially. These are now run in parallel by default (see https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview2/sdk.md#parallel-testing-and-terminal-logger-test-display) which will cause interference (due to using the same database). --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7bc876b2c..5d3752974 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -159,7 +159,7 @@ jobs: inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' - arguments: '-c Release --no-restore' + arguments: '-c Release --no-restore -p:TestTfmsInParallel=false' testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net481/net9.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket' @@ -197,7 +197,7 @@ jobs: inputs: command: 'test' projects: 'tests/IntegrationTests/IntegrationTests.csproj' - arguments: '-c Release --no-restore' + arguments: '-c Release --no-restore -p:TestTfmsInParallel=false' testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net8.0', 'No SSL') }} env: DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket' From 4329de324695288a34aa2187ea6b6eebe5432d32 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Thu, 11 Apr 2024 16:51:02 -0700 Subject: [PATCH 4/8] Use new TimeSpan.From overloads. --- src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs | 4 +++- src/MySqlConnector/Utilities/Utility.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs b/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs index f8ff90a41..ff2b17fff 100644 --- a/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs +++ b/src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs @@ -28,7 +28,9 @@ public override object ReadValue(ReadOnlySpan data, ColumnDefinitionPayloa microseconds = -microseconds; } -#if NET7_0_OR_GREATER +#if NET9_0_OR_GREATER + return TimeSpan.FromDays(days, hours, minutes, seconds, microseconds: microseconds); +#elif NET7_0_OR_GREATER return new TimeSpan(days, hours, minutes, seconds, microseconds / 1000, microseconds % 1000); #else return new TimeSpan(days, hours, minutes, seconds) + TimeSpan.FromTicks(microseconds * 10); diff --git a/src/MySqlConnector/Utilities/Utility.cs b/src/MySqlConnector/Utilities/Utility.cs index 1909c49fc..e2b4b567b 100644 --- a/src/MySqlConnector/Utilities/Utility.cs +++ b/src/MySqlConnector/Utilities/Utility.cs @@ -451,7 +451,9 @@ public static TimeSpan ParseTimeSpan(ReadOnlySpan value) seconds = -seconds; microseconds = -microseconds; } -#if NET7_0_OR_GREATER +#if NET9_0_OR_GREATER + return TimeSpan.FromHours(hours, minutes, seconds, microseconds: microseconds); +#elif NET7_0_OR_GREATER return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000, microseconds % 1000); #else return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000) + TimeSpan.FromTicks(microseconds % 1000 * 10); From 92787d15342d736a8ded40a3375c80427c608f98 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 9 Jul 2024 13:19:39 -0700 Subject: [PATCH 5/8] Remove workaround local methods. C# 13 now allows lock and ref struct within the body of async methods. https://devblogs.microsoft.com/dotnet/csharp-13-explore-preview-features/#ref-and-unsafe-in-async-methods-and-iterators --- src/MySqlConnector/Core/ServerSession.cs | 15 ++++++--------- src/MySqlConnector/MySqlConnection.cs | 18 +++++++----------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index f83c03e7a..096747c1f 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -1646,15 +1646,12 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation // first (and only) row payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); - static void ReadRow(ReadOnlySpan span, out int? connectionId, out ServerVersion? serverVersion) - { - var reader = new ByteArrayReader(span); - var length = reader.ReadLengthEncodedIntegerOrNull(); - connectionId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out int id, out _)) ? id : default(int?); - length = reader.ReadLengthEncodedIntegerOrNull(); - serverVersion = length != -1 ? new ServerVersion(reader.ReadByteString(length)) : default; - } - ReadRow(payload.Span, out var connectionId, out var serverVersion); + + var reader = new ByteArrayReader(payload.Span); + var length = reader.ReadLengthEncodedIntegerOrNull(); + var connectionId = (length != -1 && Utf8Parser.TryParse(reader.ReadByteString(length), out int id, out _)) ? id : default(int?); + length = reader.ReadLengthEncodedIntegerOrNull(); + var serverVersion = length != -1 ? new ServerVersion(reader.ReadByteString(length)) : default; // OK/EOF payload payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); diff --git a/src/MySqlConnector/MySqlConnection.cs b/src/MySqlConnector/MySqlConnection.cs index fc327a4a3..07f4288ce 100644 --- a/src/MySqlConnector/MySqlConnection.cs +++ b/src/MySqlConnector/MySqlConnection.cs @@ -1189,20 +1189,16 @@ private async Task DoCloseAsync(bool changeState, IOBehavior ioBehavior) }; connection.TakeSessionFrom(this); - ReplaceConnection(this, connection); - static void ReplaceConnection(MySqlConnection thisConnection, MySqlConnection connection) + // put the new, idle, connection into the list of sessions for this transaction (replacing this MySqlConnection) + lock (s_lock) { - // put the new, idle, connection into the list of sessions for this transaction (replacing this MySqlConnection) - lock (s_lock) + foreach (var enlistedTransaction in s_transactionConnections[connection.m_enlistedTransaction!.Transaction]) { - foreach (var enlistedTransaction in s_transactionConnections[connection.m_enlistedTransaction!.Transaction]) + if (enlistedTransaction.Connection == this) { - if (enlistedTransaction.Connection == thisConnection) - { - enlistedTransaction.Connection = connection; - enlistedTransaction.IsIdle = true; - break; - } + enlistedTransaction.Connection = connection; + enlistedTransaction.IsIdle = true; + break; } } } From 8c01776434151e21889f312d2a78424314c50b93 Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 9 Jul 2024 14:01:38 -0700 Subject: [PATCH 6/8] Reflect SSL test failure under .NET 9.0. This may be related to this release note: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md#tls-resume-with-client-certificates-on-linux --- tests/IntegrationTests/SslTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/IntegrationTests/SslTests.cs b/tests/IntegrationTests/SslTests.cs index e22c034fb..96f0421ed 100644 --- a/tests/IntegrationTests/SslTests.cs +++ b/tests/IntegrationTests/SslTests.cs @@ -103,7 +103,9 @@ private async Task DoTestSsl(string connectionString) Assert.True(connection.SslIsEncrypted); Assert.True(connection.SslIsSigned); Assert.True(connection.SslIsAuthenticated); +#if !NET9_0_OR_GREATER Assert.True(connection.SslIsMutuallyAuthenticated); +#endif #endif cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; var sslVersion = (string) await cmd.ExecuteScalarAsync(); @@ -133,9 +135,11 @@ public async Task ConnectSslClientCertificateFromCertificateStore(string certFil Assert.True(connection.SslIsEncrypted); Assert.True(connection.SslIsSigned); Assert.True(connection.SslIsAuthenticated); +#if !NET9_0_OR_GREATER Assert.True(connection.SslIsMutuallyAuthenticated); #endif - cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; +#endif + cmd.CommandText = "SHOW SESSION STATUS LIKE 'Ssl_version'"; var sslVersion = (string) await cmd.ExecuteScalarAsync(); Assert.False(string.IsNullOrWhiteSpace(sslVersion)); } From 5151359726dcc394d727a1ad37c7dd95f5eb46ca Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Tue, 13 Aug 2024 21:17:57 -0700 Subject: [PATCH 7/8] Update to .NET 9 Preview 7. Signed-off-by: Bradley Grainger --- src/MySqlConnector/Core/ServerSession.cs | 15 +++++++++++++-- tests/IntegrationTests/SslTests.cs | 8 ++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index 170e1c64d..9434092a8 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -7,6 +7,7 @@ using System.Net.Security; using System.Net.Sockets; using System.Reflection; +using System.Runtime.ConstrainedExecution; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -1280,7 +1281,11 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect { try { +#if NET9_0_OR_GREATER + var certificate = X509CertificateLoader.LoadPkcs12FromFile(cs.CertificateFile, cs.CertificatePassword, X509KeyStorageFlags.MachineKeySet); +#else var certificate = new X509Certificate2(cs.CertificateFile, cs.CertificatePassword, X509KeyStorageFlags.MachineKeySet); +#endif if (!certificate.HasPrivateKey) { certificate.Dispose(); @@ -1352,7 +1357,9 @@ private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, Connect { // load the certificate at this index; note that 'new X509Certificate' stops at the end of the first certificate it loads Log.LoadingCaCertificate(m_logger, Id, index); -#if NET5_0_OR_GREATER +#if NET9_0_OR_GREATER + var caCertificate = X509CertificateLoader.LoadCertificate(certificateBytes.AsSpan(index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index)); +#elif NET5_0_OR_GREATER var caCertificate = new X509Certificate2(certificateBytes.AsSpan(index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index), default(ReadOnlySpan), X509KeyStorageFlags.MachineKeySet); #else var caCertificate = new X509Certificate2(Utility.ArraySlice(certificateBytes, index, (nextIndex == -1 ? certificateBytes.Length : nextIndex) - index), default(string), X509KeyStorageFlags.MachineKeySet); @@ -1522,7 +1529,11 @@ X509CertificateCollection LoadCertificate(string sslKeyFile, string sslCertifica // Schannel has a bug where ephemeral keys can't be loaded: https://github.com/dotnet/runtime/issues/23749#issuecomment-485947319 // The workaround is to export the key (which may make it "Perphemeral"): https://github.com/dotnet/runtime/issues/23749#issuecomment-739895373 var oldCertificate = m_clientCertificate; +#if NET9_0_OR_GREATER + m_clientCertificate = X509CertificateLoader.LoadPkcs12(m_clientCertificate.Export(X509ContentType.Pkcs12, default(string?)), null); +#else m_clientCertificate = new X509Certificate2(m_clientCertificate.Export(X509ContentType.Pkcs12)); +#endif oldCertificate.Dispose(); } return [m_clientCertificate]; @@ -1593,7 +1604,7 @@ X509CertificateCollection LoadCertificate(string sslKeyFile, string sslCertifica throw new MySqlException("Could not load the client key from " + sslCertificateFile, ex); } #endif - } + } } #if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER diff --git a/tests/IntegrationTests/SslTests.cs b/tests/IntegrationTests/SslTests.cs index 96f0421ed..344bd051e 100644 --- a/tests/IntegrationTests/SslTests.cs +++ b/tests/IntegrationTests/SslTests.cs @@ -64,7 +64,11 @@ public async Task ConnectSslClientCertificateCallback(string certificateFile, st using var connection = new MySqlConnection(csb.ConnectionString); connection.ProvideClientCertificatesCallback = x => { +#if NET9_0_OR_GREATER + x.Add(X509CertificateLoader.LoadPkcs12FromFile(certificateFilePath, certificateFilePassword)); +#else x.Add(new X509Certificate2(certificateFilePath, certificateFilePassword)); +#endif return default; }; @@ -119,7 +123,11 @@ public async Task ConnectSslClientCertificateFromCertificateStore(string certFil // Create a mock of certificate store var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); +#if NET9_0_OR_GREATER + var certificate = X509CertificateLoader.LoadPkcs12FromFile(Path.Combine(AppConfig.CertsPath, certFile), null); +#else var certificate = new X509Certificate2(Path.Combine(AppConfig.CertsPath, certFile)); +#endif store.Add(certificate); var csb = AppConfig.CreateConnectionStringBuilder(); From 3ae8fc1bc0d6ceb477a84b495b08fda2f58e04fb Mon Sep 17 00:00:00 2001 From: Bradley Grainger Date: Sat, 24 Aug 2024 12:38:37 -0700 Subject: [PATCH 8/8] Remove unnecessary usings. Signed-off-by: Bradley Grainger --- src/MySqlConnector/Core/ServerSession.cs | 1 - src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs | 1 - tests/IntegrationTests/RedirectionTests.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/MySqlConnector/Core/ServerSession.cs b/src/MySqlConnector/Core/ServerSession.cs index b38860da5..d6ed98905 100644 --- a/src/MySqlConnector/Core/ServerSession.cs +++ b/src/MySqlConnector/Core/ServerSession.cs @@ -7,7 +7,6 @@ using System.Net.Security; using System.Net.Sockets; using System.Reflection; -using System.Runtime.ConstrainedExecution; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; diff --git a/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs b/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs index 77e20d081..259f3f963 100644 --- a/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs +++ b/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs @@ -1,6 +1,5 @@ using System.Buffers; using MySqlConnector.Protocol.Payloads; -using MySqlConnector.Utilities; namespace MySqlConnector.Protocol.Serialization; diff --git a/tests/IntegrationTests/RedirectionTests.cs b/tests/IntegrationTests/RedirectionTests.cs index 892f1eb7f..f5f374eb9 100644 --- a/tests/IntegrationTests/RedirectionTests.cs +++ b/tests/IntegrationTests/RedirectionTests.cs @@ -1,5 +1,4 @@ #if !MYSQL_DATA -using System.Globalization; using System.Net; using System.Net.Sockets;