madelson/DistributedLock

Throw a specific exception for deadlocks

oliverhanappi opened this issue · 3 comments

The method SqlApplicationLock.ParseExitCode throws an InvalidOperationException when a deadlock is detected. It would be very useful if a specific exception would be thrown.

Hi @oliverhanappi thank you for your interest in the library.

I'm curious whether you've ever seen the deadlock exit code returned. I remember trying to force this a while back and not seeing it.

Provided we can create a test case where this is actually returned, this suggestion makes sense and would be an easy enhancement. For backwards compat, the new exception should extend InvalidOperationException.

It is actually pretty easy. You just need to acquire multiple locks within the same connection or transaction such that two connections/transactions are waiting on each other. Here is a simple repro:

using System;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
using Medallion.Threading.Sql;

namespace SqlDistributedLockDeadlock
{
  public static class Program
  {
    public static void Main ()
    {
      using (var barrier = new Barrier (2))
      {
        var task1 = Task.Run (() => WithTransaction((connection, transaction) =>
        {
          new SqlDistributedLock ("test1", transaction).Acquire();
          barrier.SignalAndWait();
          new SqlDistributedLock ("test2", transaction).Acquire();
        }));
        
        var task2 = Task.Run (() => WithTransaction((connection, transaction) =>
        {
          new SqlDistributedLock ("test2", transaction).Acquire();
          barrier.SignalAndWait();
          new SqlDistributedLock ("test1", transaction).Acquire();
        }));

        Task.WaitAll (task1, task2);
      }
    }

    private static void WithTransaction (Action<SqlConnection, SqlTransaction> action)
    {
      const string connectionString = "Server=localhost;Database=Playground;Integrated Security=True";

      using (var connection = new SqlConnection (connectionString))
      {
        connection.Open();

        using (var transaction = connection.BeginTransaction())
          action.Invoke (connection, transaction);

        connection.Close();
      }
    }
  }
}

This is addressed as of version 1.4