-
Notifications
You must be signed in to change notification settings - Fork 83
Open
Description
Hi and sorry for the relatively low effort bug report. (which I think is a bug, in how it is analyzed?)
I think through the way Coyote is executing the test it produces the following error (which never occurs when executing the code normally):
The active test run was aborted. Reason: Test host process crashed : Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Coyote.Rewriting.Types.Threading.Monitor.SynchronizedBlock.EnterLock()
at Microsoft.Coyote.Rewriting.Types.Threading.Monitor.SynchronizedBlock.Lock(Object syncObject)
at Microsoft.Coyote.Rewriting.Types.Threading.Monitor.Enter(Object obj, Boolean& lockTaken)
at SVN.MacTool.LdapBase.Connect.LdapConnectionPool.CleanupPool(Object state) in C:\Develop\TFS\SVN.MacTool\src\SVN.MacTool.LdapBase\Connect\LdapConnectionPool.cs:line 179
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
at System.Threading.TimerQueue.FireNextTimers()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
Test Run Aborted.
When testing this class
using System.Collections.Concurrent;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using SVN.MacTool.Common.EventBus;
using SVN.MacTool.Common.EventBus.Messages;
using SVN.MacTool.LdapBase.Configuration;
namespace SVN.MacTool.LdapBase.Connect;
public class LdapConnectionPool : ILdapConnectionPool, IConnect
{
private readonly ConcurrentDictionary<int, LdapConnectionWrapper> _pool = new();
private readonly Timer _cleanupTimer;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
/// <summary>
/// The configuration
/// </summary>
private readonly ILdapConfiguration _configuration;
/// <summary>
/// The logger
/// </summary>
protected readonly ILogger<LdapConnectionPool> _logger;
private readonly object _lock = new object();
public LdapConnectionPool(ILogger<LdapConnectionPool> logger, ILdapConfiguration configuration)
{
this._logger = logger;
this._configuration = configuration;
this._cleanupTimer = new Timer(this.CleanupPool, null, configuration.ConnectionIdleTimeout, configuration.ConnectionIdleTimeout);
}
/// <summary>
/// Connects to Ldap Server.
/// </summary>
/// <returns>LdapConnection.</returns>
public async Task<LdapConnection> ConnectAsync()
{
await this._semaphore.WaitAsync();
{
try
{
var connection = this.GetConnection();
if (connection != null)
{
connection.Bind();
return connection;
}
else
{
this._logger.LogError(
$"with Config: {this._configuration.Server} and Port: {this._configuration.Port}");
throw new Exception("Keine Verbindung zu Ldap-Server möglich");
}
}
catch (Exception e)
{
this._logger.LogError(e.Message, e);
this._logger.LogError(
$"with Config: {this._configuration.Server} and Port: {this._configuration.Port}");
throw;
}
finally
{
this._semaphore.Release();
}
}
}
public LdapConnection GetConnection()
{
// Wenn Verbindungen aus dem Pool abrufen werden, müssen wir verhindern,
// dass die gleiche Verbindung gleichzeitig an mehrere Anforderer ausgegeben wird.
lock (this._lock)
{
var connection = this._pool.FirstOrDefault(p => p.Value.inUse == false);
if (connection.Value != null)
{
connection.Value.inUse = true;
connection.Value.LastAccessed = DateTime.UtcNow;
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]: Take connection from pool with ID [{connection.Value.Connection.GetHashCode()}] ");
return connection.Value.Connection;
}
// Kein Treffer ? Dann neue Verbindung
var newConnection = this.CreateNewConnection().Connection;
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Create connection with ID [{newConnection.GetHashCode()}] ");
return newConnection;
}
}
public void ReturnConnection(LdapConnection connection)
{
if (connection == null) throw new ArgumentNullException(nameof(connection));
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Enter ReturnConnection with Connection ID [{connection.GetHashCode()}] ");
var key = connection.GetHashCode();
// LdapConnectionWrapper wrapper = new LdapConnectionWrapper(connection);
// bool foundConnection = this._pool.TryGetValue(key, out wrapper);
if (this._pool.TryGetValue(key, out LdapConnectionWrapper wrapper))
{
// Mark the connection as not in use if it's found in the pool
wrapper.inUse = false;
wrapper.LastAccessed = DateTime.UtcNow;
this._logger.LogDebug($"Return existing connection to pool with ID {key}.");
}
else
{
// Only add new connections to the pool if under max size
if (this._pool.Count < this._configuration.MaxPoolSize)
{
var added = this._pool.TryAdd(key, new LdapConnectionWrapper(connection) { inUse = false, LastAccessed = DateTime.UtcNow });
if (added)
{
this._logger.LogDebug($"Added new connection to pool with ID {key}.");
}
}
else
{
// Dispose of the connection if the pool is full
this._logger.LogDebug($"Pool is full. Disposing connection with ID {key}.");
connection.Dispose();
}
}
}
private LdapConnectionWrapper CreateNewConnection()
{
lock (this._lock)
{
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Create new connection ... ");
this._logger.LogDebug(
$"[{Thread.CurrentThread.ManagedThreadId}]:Connecting with SecureBasic to: {this._configuration.Server} and Port: {this._configuration.Port}");
var ldapIdentifier = new LdapDirectoryIdentifier(this._configuration.Server, this._configuration.Port);
LdapConnection connection = new LdapConnection(ldapIdentifier)
{
AuthType = AuthType.Basic,
Credential = new NetworkCredential(this._configuration.Username, this._configuration.Password),
SessionOptions =
{
ProtocolVersion = 3,
// Specifies usage of "ldaps://" scheme
SecureSocketLayer = true,
}
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// TODO: Remove only for development!
// This option is not working on Linux: https://github.com/dotnet/runtime/issues/60972
connection.SessionOptions.VerifyServerCertificate = (ldapConnection, certificate) => true;
}
return new(connection);
}
}
private void CleanupPool()
{
lock (this._lock)
{
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Try to cleanup connection pool ... ");
// Überprüfe und entferne Verbindungen, die länger als _config.ConnectionIdleTimeout ungenutzt sind
var now = DateTime.UtcNow;
foreach (var wrapper in this._pool)
{
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Evaluating Connection with ID [{wrapper.Value.Connection.GetHashCode()}] ");
if ((now - wrapper.Value.LastAccessed) > this._configuration.ConnectionIdleTimeout)
{
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Cleanup connection with ID [{wrapper.Value.Connection.GetHashCode()}] ");
if (this._pool.TryRemove(wrapper.Value.Connection.GetHashCode(), out var wrapperToRemove))
{
wrapperToRemove.Connection.Dispose();
}
}
}
}
}
public void Dispose()
{
// this._cleanupTimer.Dispose();
foreach (var wrapper in this._pool)
{
this._logger.LogDebug($"[{Thread.CurrentThread.ManagedThreadId}]:Dispose connection with ID [{wrapper.Value.Connection.GetHashCode()}] ");
wrapper.Value.Connection.Dispose();
}
}
}
With the following test:
private async Task CoyoteTest_Pool()
{
// Arrange
var ldapConnectionPool = new LdapConnectionPool(this.CreateLogger<LdapConnectionPool>(), new LdapConfiguration(){});
// Act
var connection1 = ldapConnectionPool.GetConnection();
ldapConnectionPool.ReturnConnection(connection1);
var connection2 = Task.Run(() =>
{
return ldapConnectionPool.GetConnection();
});
var connection3 = Task.Run(() =>
{
return ldapConnectionPool.GetConnection();
});
await Task.WhenAll(connection2, connection3);
// Assert
connection2.Result.Should().NotBeSameAs(connection3.Result);
}
Metadata
Metadata
Assignees
Labels
No labels