Skip to content

SqlServerDistributedLock won't work correctly in async task #662

@pieceofsummer

Description

@pieceofsummer

It is currently not an issue for DisableConcurrentExecutionAttribute, since it wraps a parent task (which Hangfire runs synchronously), but it can become one in the future.

Consider the following test:

private async Task AsyncChildTask()
{
    using (var connection = ConnectionUtils.CreateConnection())
    {
        var storage = CreateStorage(connection);
        using (var @lock = new SqlServerDistributedLock(storage, "test", _timeout))
        {
            var AcquiredLocksField = typeof(SqlServerDistributedLock)
                .GetField("AcquiredLocks", BindingFlags.Static | BindingFlags.NonPublic);

            var AcquiredLocks = (ThreadLocal<Dictionary<string, int>>)AcquiredLocksField.GetValue(null);

            Assert.True(AcquiredLocks.Value.ContainsKey("test"));

            await Task.Yield();

            Assert.True(AcquiredLocks.Value.ContainsKey("test"), "Lock is not owned"); // False!
        }
    });

}

[Fact]
public void ParentTaskCallingAsyncChild()
{
    Task.WaitAll(AsyncChildTask());
}

It will fail the second assertion, because task may continue on different thread, so using ThreadLocal<> is incorrect for storing owned locks. You should use AsyncLocal<> instead (CallContext on older frameworks).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions