Skip to content

Bug: RepoDb.SqlServer 1.13.0 - Memory leak in Microsoft.Data.SqlClient 5.0.1 #1128

@paaaz

Description

@paaaz

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfixedThe bug, issue, incident has been fixed.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions