C#C
C#2y ago
Ploxi

Threadsafety and Interlocked.CompareExchange

Hey, i have written a method that updates a dictionary without locks. Of course i have written a test for it and it succeeded for 15 runs. But today i executed all tests and suddendly the test failed, which i cannot reproduce a second time. Can you guys spot what i might have done wrong?

class UpdateQueueStatus
{
    private static Dictionary<string, UpdateQueueStatus> _mappings = new(StringComparer.OrdinalIgnoreCase);

    public static readonly UpdateQueueStatus None = Create(0);
    public static readonly UpdateQueueStatus Pending = Create(1);
    public static readonly UpdateQueueStatus Queued = Create(0);
    public static readonly UpdateQueueStatus Enqueued = Create(1);
    public static readonly UpdateQueueStatus Caching = Create(2);
    public static readonly UpdateQueueStatus Cached = Create(3);
    public static readonly UpdateQueueStatus Applying = Create(4);
    public static readonly UpdateQueueStatus Applied = Create(5);
    public static readonly UpdateQueueStatus Completed = Create(6);
    public static readonly UpdateQueueStatus Failed = Create(7);
    public static readonly UpdateQueueStatus Cancelled = Create(8);
    public static readonly UpdateQueueStatus Downloading = Create(2);

    public string Value { get; }

    public UpdateQueueStatus(string value)
    {
        Value = value;
    }


    public static UpdateQueueStatus Parse(string value)
    {
        if (_mappings.TryGetValue(value, out var status))
        {
            return status;
        }

        var result = new UpdateQueueStatus(value);

        while (true)
        {
            var currentMap = _mappings;

            var newMap = new Dictionary<string, UpdateQueueStatus>(currentMap, StringComparer.OrdinalIgnoreCase);
            newMap.TryAdd(value, result);
            if (Interlocked.CompareExchange(ref _mappings, newMap, currentMap) == currentMap)
            {
                break;
            }
        }

        return result;
    }

    private static UpdateQueueStatus Create(int number, [CallerMemberName] string? name = null)
    {
        var result = new UpdateQueueStatus(name ?? "None");
        _mappings.TryAdd(number.ToString(), result);
        _mappings.TryAdd(result.Value, result);
        _intMappings.Add((result.Value, number));
        return result;
    }
}

// Test:

public class Test
{
    [Fact]
    public void Should_Be_Threadsafe()
    {
        var count = UpdateQueueStatus.GetMappings().Count;

        var tasks = new List<Task>();
        for (int i = 0; i < 100; i++)
        {
            var task = Task.Run(() =>
            {
                for (int j = 0; j < 100; j++)
                {
                    UpdateQueueStatus.Parse("value" + j);
                }
            });
            tasks.Add(task);
        }
        Task.WaitAll(tasks.ToArray());

        //Console.WriteLine("Number of keys in the dictionary: " + UpdateQueueStatus.GetMappings());
        Assert.Equal(count + 100, UpdateQueueStatus.GetMappings().Count);
    }
}


Test failure:
The test failed:
Message: 
Assert.Equal() Failure
Expected: 125
Actual: 121
Was this page helpful?