Skip to content

CancellationToken registration leak, regression #1810

@tlecomte

Description

@tlecomte

Describe the bug

After updating to SqlClient 5.0.1, we observe a memory leak in our application. Memory dumps show a huge amount of CancellationTokenSource+CallbackNode entries (we had a dump with 2 GB of those), referencing SqlCommand objects.

We believe this might be due to the changes in:

With the changes in these PRs, it seems the CancellationToken registration is not always disposed. There are several if blocks that may return before the registration object gets to be saved in the context. And exceptions could also be thrown.

To reproduce

using Microsoft.Data.SqlClient; // 5.0.1
using System.Threading;
using System.Threading.Tasks;

public static class Program
{
    public static async Task Main()
    {
        const string query = "SELECT 1";

        // this could be the application lifetime cancellation
        // or another form of global shared cancellation
        using var longRunningCts = new CancellationTokenSource();

        var connectionString = new SqlConnectionStringBuilder
        {
            DataSource = "(local)",
            IntegratedSecurity = true,
            TrustServerCertificate = true,
        }.ToString();

        try
        {
            using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(longRunningCts.Token))
            using (var connection = new SqlConnection(connectionString))
            {
                await connection.OpenAsync(linkedCts.Token);

                using (var command = new SqlCommand(query, connection))
                using (var reader = await command.ExecuteReaderAsync(linkedCts.Token))
                {
                    linkedCts.Cancel();

                    while (await reader.ReadAsync(linkedCts.Token))
                    {

                    }
                }
            }
        }
        catch (Exception e)
        {

        }

        // => attach a debugger here
        // linkedCts as well as all the SQL objects have been disposed above
        // however a memory snapshot shows that the CancellationTokenSource+Registrations and CancellationTokenSource+CallbackNode are still there, including the objects they reference
        // so garbage collection cannot happen correctly
    }
}

Expected behavior

No memory leak, CancellationTokenSource+CallbackNode entries are not present in a dump.

Further technical details

Microsoft.Data.SqlClient version: 5.0.1
.NET target: .NET 6.0.3
SQL Server version: SQL Server 2019
Operating system: Windows 2019

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    Regression 💥Issues that are regressions introduced from earlier PRs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions