Bug Description
Unfortunately Microsoft.Data.SqlClient in version 5.0.1 has a severe memory leak, which also leads to leaking memory using RepoDb.SqlServer v1.13.0.
Details: Issue #1810 on SqlClient Github repo
Reproduction Steps
Use RepoDb Async calls in a periodic background service adapted from code example dotnet/SqlClient#1810 (comment)
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using RepoDb;
public class MemLeakingService : BackgroundService
{
private readonly string connectionString;
public MemLeakingService(IConfiguration configuration)
{
this.connectionString = configuration.GetConnectionString("Database");
}
protected override async Task ExecuteAsync(CancellationToken token)
{
Console.WriteLine($"SqlClient version: {FileVersionInfo.GetVersionInfo(typeof(SqlCommand).Assembly.Location).ProductVersion}");
while (true)
{
const string sql = "SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)";
using (var sqlConnection = new SqlConnection(this.connectionString))
{
await sqlConnection.ExecuteQueryAsync(sql, cancellationToken: token).ConfigureAwait(false);
}
GC.Collect(); // for demonstration only; removing this line does not change output
Console.WriteLine($"Lost registrations: {CountLostSqlCommands(token)}");
}
}
static int CountLostSqlCommands(CancellationToken token)
{
var cts = (CancellationTokenSource)typeof(CancellationToken).GetField("_source", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(token)!;
var registrationsField = typeof(CancellationTokenSource).GetField("_registrations", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
var registrations = registrationsField.GetValue(cts);
if (registrations == null)
return 0;
var callbacksProperty = registrationsField.FieldType.GetField("Callbacks", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)!;
var callbackNode = callbacksProperty.GetValue(registrations);
var callbackNodeType = callbacksProperty.FieldType;
var nextProperty = callbackNodeType.GetField("Next", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)!;
var stateProperty = callbackNodeType.GetField("CallbackState", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)!;
int lostSqlCommands = 0;
while (callbackNode != null)
{
var callbackState = stateProperty.GetValue(callbackNode);
if (typeof(SqlCommand) == callbackState?.GetType())
{
lostSqlCommands += 1;
}
callbackNode = nextProperty.GetValue(callbackNode);
}
return lostSqlCommands;
}
}
Resolution
Update Microsoft.Data.SqlClient to 5.1.0 as soon as the preview version is marked as stable (should be soon)
Library Version:
RepoDb v1.13.0 and RepoDb.SqlServer v1.13.0
Bug Description
Unfortunately Microsoft.Data.SqlClient in version 5.0.1 has a severe memory leak, which also leads to leaking memory using RepoDb.SqlServer v1.13.0.
Details: Issue #1810 on SqlClient Github repo
Reproduction Steps
Use RepoDb Async calls in a periodic background service adapted from code example dotnet/SqlClient#1810 (comment)
Resolution
Update Microsoft.Data.SqlClient to 5.1.0 as soon as the preview version is marked as stable (should be soon)
Library Version:
RepoDb v1.13.0 and RepoDb.SqlServer v1.13.0