Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 106 additions & 11 deletions MySQL.Data/src/MySqlPoolManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,118 @@ private static string GetKey(MySqlConnectionStringBuilder settings)

public static async Task<MySqlPool> GetPoolAsync(MySqlConnectionStringBuilder settings, bool execAsync, CancellationToken cancellationToken)
{
string text = GetKey(settings);
var poolKey = GetKey(settings);

SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
semaphoreSlim.Wait(CancellationToken.None);
MySqlPool pool;
Pools.TryGetValue(text, out pool);
if (Pools.TryGetValue(poolKey, out var pool) == false)
{
var poolFactory = PoolFactory.GetFactory(settings);

return await poolFactory.GetMySqlPoolAsync(settings, cancellationToken);
}

if (pool == null)
return pool;
}

private class PoolFactory
{
private readonly MySqlConnectionStringBuilder settings;
private readonly string poolKey;

readonly SemaphoreSlim tryStartCreatingPoolSemaphore = new SemaphoreSlim(1);
readonly ConcurrentQueue<TaskCompletionSource<MySqlPool>> poolAwaiters = new ConcurrentQueue<TaskCompletionSource<MySqlPool>>();

public PoolFactory(MySqlConnectionStringBuilder settings, string poolKey)
{
pool = await MySqlPool.CreateMySqlPoolAsync(settings, execAsync, cancellationToken).ConfigureAwait(false);
Pools.Add(text, pool);
this.settings = settings;
this.poolKey = poolKey;
}
else

static readonly ConcurrentDictionary<string, PoolFactory> Factories = new();

public static PoolFactory GetFactory(MySqlConnectionStringBuilder settings)
{
var poolKey = GetKey(settings);

GET_FACTORY:
if (Factories.TryGetValue(poolKey, out var factory) == false)
{
if (Factories.TryAdd(poolKey, factory = new PoolFactory(settings, poolKey)) == false)
{
goto GET_FACTORY;
}
}

return factory;
}

public async Task<MySqlPool> GetMySqlPoolAsync(MySqlConnectionStringBuilder settings, CancellationToken cancellationToken)
{
TaskCompletionSource<MySqlPool> poolTaskCompletionSource = new TaskCompletionSource<MySqlPool>();

if (Pools.TryGetValue(poolKey, out var pool) == false)
{
poolAwaiters.Enqueue(poolTaskCompletionSource);

_ = TryStartCreatingPoolAsync();

pool = await poolTaskCompletionSource.Task;
}

pool.Settings = settings;

semaphoreSlim.Release();
return pool;
return pool;
}

public async Task TryStartCreatingPoolAsync()
{
MySqlPool pool;

if (tryStartCreatingPoolSemaphore.Wait(0) == false)
{
return;
}

try
{

if (Pools.TryGetValue(poolKey, out pool) == false)
{
pool = await MySqlPool.CreateMySqlPoolAsync(settings, true, default).ConfigureAwait(false);

Pools.Add(poolKey, pool);
}

DispatchPoolAwaiters(pool);
}
finally
{
tryStartCreatingPoolSemaphore.Release();
}

if (tryStartCreatingPoolSemaphore.Wait(0) == false)
{
return;
}

try
{
DispatchPoolAwaiters(pool);

Factories.TryRemove(poolKey, out _);
}
finally
{
tryStartCreatingPoolSemaphore.Release();
}
}

private void DispatchPoolAwaiters(MySqlPool pool)
{
while (poolAwaiters.TryDequeue(out var poolAwaiter))
{
poolAwaiter.SetResult(pool);
}
}
}

public static void RemoveConnection(Driver driver)
Expand Down
16 changes: 15 additions & 1 deletion MySQL.Data/tests/MySql.Data.Tests/PoolingTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
// Copyright (c) 2013, 2022, Oracle and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -47,13 +48,26 @@ internal override void AdjustConnectionSettings(MySqlConnectionStringBuilder set
[Test]
public void BasicConnection()
{
// test that no exception is thrown in parallel start up
Enumerable.Range(0, 2)
.AsParallel()
.ForAll(_ =>
{
// Min Pool Size=10 options is required.
// Otherwise error on creating MySqlPool cannot be reproduced.
// Even with Task.Delay. To slowdown await time on pool creation. That's strange...
var c = new MySqlConnection(Settings.ConnectionString + ";Min Pool Size=10");
c.Open();
KillConnection(c);
c.Close();
});

MySqlConnection c = new MySqlConnection(Settings.ConnectionString);
c.Open();
int serverThread = c.ServerThread;
c.Close();

// first test that only a single connection get's used
// test that only a single connection get's used
for (int i = 0; i < 10; i++)
{
c = new MySqlConnection(Settings.ConnectionString);
Expand Down