Creating a cross-computer mutex using SQL Server

locking, sql-server, sql-server-2008, synchronization

Solution

I have put together a little class will test and feedback

public class GlobalMutex
{
    private SqlCommand _sqlCommand;
    private SqlConnection _sqlConnection;

    string sqlCommandText = @"

        declare @result int
        exec @result =sp_getapplock @Resource=@ResourceName, @LockMode='Exclusive', @LockOwner='Transaction', @LockTimeout = @LockTimeout

    ";

    public GlobalMutex(SqlConnection sqlConnection, string unqiueName, TimeSpan lockTimeout)
    {
        _sqlConnection = sqlConnection;
        _sqlCommand = new SqlCommand(sqlCommandText, sqlConnection);
        _sqlCommand.Parameters.AddWithValue("@ResourceName", unqiueName);
        _sqlCommand.Parameters.AddWithValue("@LockTimeout", lockTimeout.TotalMilliseconds);
    }

    private readonly object _lockObject = new object();
    private Locker _lock = null;
    public Locker Lock
    {
        get
        {
            lock(_lockObject)
            {
                if (_lock != null)
                {
                    throw new InvalidOperationException("Unable to call Lock twice"); // dont know why
                }
                _lock = new Locker(_sqlConnection, _sqlCommand);
            }
            return _lock;
        }
    }

    public class Locker : IDisposable
    {
        private SqlTransaction _sqlTransaction;
        private SqlCommand _sqlCommand;

        internal Locker(SqlConnection sqlConnection, SqlCommand sqlCommand)
        {
            _sqlCommand = sqlCommand;
            _sqlTransaction = sqlConnection.BeginTransaction();
            _sqlCommand.Transaction = _sqlTransaction;
            int result = sqlCommand.ExecuteNonQuery();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing) 
            {
                _sqlTransaction.Commit(); // maybe _sqlTransaction.Rollback() might be slower
            }
        }
    }
}

Usage is:

GlobalMutex globalMutex = new GlobalMutex(
    new SqlConnection(""),
    "myGlobalUniqueLockName",
    new TimeSpan(0, 1, 0)
);


using (globalMutex.Lock)
{
    // do work.
}

Problem

I have a few computers using the same database (SQL Server 2008) I'm trying to synchronize a task between all these computers using the database. Each task is represented by a guid that is the lock-id (if comparing to Mutex, that would be the Mutex name) I have a few thoughts, but I think they are kind of hacks, and was hoping someone here would have a better solution: - Create a new table "Locks" each row consists of a guid, lock the table row exclusively in a transaction - and complete/revert the transaction when finished. - use the `sp_getapplock` in a transaction where the lock name is the lock-id guid I think that holding a transaction running is not so good... I thought maybe there's a solution that does not require me to hold an open transaction or session?

Original source