Skip to content

Instantly share code, notes, and snippets.

@cheenamalhotra
Created November 4, 2020 05:42
Show Gist options
  • Save cheenamalhotra/638338ccc4dc83a7b15381fbd261fd38 to your computer and use it in GitHub Desktop.
Save cheenamalhotra/638338ccc4dc83a7b15381fbd261fd38 to your computer and use it in GitHub Desktop.
Test Transaction Leak - no repro
using System;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using System.Data;
using Microsoft.Data.SqlClient;
namespace TestTransactionLeak
{
public class SqlContext : IDisposable, IAsyncDisposable
{
private readonly CancellationToken _cancellationToken;
private readonly string _connectionString;
private DbConnection _sqlConnection;
private DbTransaction _sqlTransaction;
public SqlContext(string connectionString, CancellationToken cancellationToken)
{
_connectionString = connectionString;
_cancellationToken = cancellationToken;
}
public ValueTask DisposeAsync()
{
try
{
Dispose();
return default;
}
catch (Exception exception)
{
return new ValueTask(Task.FromException(exception));
}
}
public void Dispose()
{
_sqlTransaction?.Dispose();
_sqlTransaction = null;
_sqlConnection?.Dispose();
_sqlConnection = null;
}
public async Task<DbConnection> SqlConnection() => _sqlConnection ??= await CreateConnection();
private async Task<DbTransaction> SqlTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified) =>
_sqlTransaction ??= await CreateTransaction(isolationLevel);
public Task<DbTransaction> SqlTransactionWithNoLock() => SqlTransaction(IsolationLevel.ReadUncommitted);
private async Task<SqlConnection> CreateConnection()
{
var result = new SqlConnection(_connectionString);
await result.OpenAsync(_cancellationToken);
return result;
}
private async Task<DbTransaction> CreateTransaction(IsolationLevel isolationLevel) =>
await (await SqlConnection()).BeginTransactionAsync(
isolationLevel, _cancellationToken);
}
public static class Program
{
private static string s_connString = "<connection_string>";
public static void Main()
{
// Runs Managed SNI on Windows - enables same codebase that runs on linux.
AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true);
RunTest().Wait();
}
private async static Task RunTest()
{
for (int i = 0; i < 5; i++)
{
var cancellationToken = new CancellationToken();
await using SqlContext context = CreateContext(cancellationToken);
await using DbConnection connection = await context.SqlConnection();
await using DbTransaction transaction = await context.SqlTransactionWithNoLock();
await RunTestAsync(connection,
"SELECT @@TRANCOUNT",
cancellationToken: cancellationToken,
transaction: transaction
);
}
}
private static async Task RunTestAsync(DbConnection connection, string commandText, CancellationToken cancellationToken, DbTransaction transaction)
{
DbCommand command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = commandText;
await using DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync())
{
Console.WriteLine((connection as SqlConnection).ServerProcessId + " : Open Transactions = " + reader.GetValue(0));
}
}
private static SqlContext CreateContext(CancellationToken cancellationToken) => new SqlContext(s_connString, cancellationToken);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment