Good day. I hope you are doing well. I'm trying to perform a unit test to test a Wrapper for a data structure I created (SortedList). This Wrapper is "thread-safe". First, I conducted a test where 10 threads accessed and added information to see if there was concurrency. Initially, I tested an unsynchronized list, and in this case, it was expected to have concurrency issues, which indeed happened. Then, I applied a synchronized list and the problem was resolved. Here is the code for the test (If you think it can be improved, any suggestions would be greatly appreciated).
Test Case:
//Arrange
SortedList<int> list = new();
var synchronizedList = list.Synchronized();
var expected = new SortedList<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int len = 10;
ParallelOptions options = new()
{
MaxDegreeOfParallelism = len
};
//Act
Parallel.For(0, len, options, synchronizedList.Add);
//Asserts
Assert.Equal(len, list.Count);
Assert.Equal(expected, list);
In this case, there was no issue; everything worked as expected. The problem arises when I want to create the typical concurrency example, where N threads are created and they increase a value N times. In this example, there must be concurrency without fail. So, I prepared a small test case to verify if it can handle such a problem. Here is the code:
Test Case 2:
//Arrange
SortedList<int> list = new()
{
0
};
var synchronizedList = list.Synchronized();
int len = 2, maxIter = 1;
ParallelOptions options = new()
{
MaxDegreeOfParallelism = len
};
//Act
Parallel.For(0, len, options, i =>
{
for (int j = 0; j != maxIter; ++j)
{
synchronizedList[0]++;
}
});
//Asserts
Assert.Equal(maxIter * len, list[0]);
When I tested the test case, for some strange reason, the expected result was 2 (I initially tested with 10,000 iterations and 10 threads). However, it was giving me 1. Perplexed, I went to check the code for the issue, but I didn't see any. Here is the code snippet of the indexer in the Wrapper class:
Index:
public T this[int index]
{
get
{
lock (_lock)
{
return _sortedList[index];
}
}
set
{
lock (_lock)
{
_sortedList[index] = value;
}
}
}
As we can see, i have the lock, so it accesses safely. In fact, I did a debug test, and in that case, it passes the test. After this, I went to the code where I initialize the _lock.
Constructor:
internal SynchronizedSortedList(SortedList<T> sortedList)
{
_sortedList = sortedList;
_lock = _sortedList.SyncRoot;
}
After verifying that everything was "fine", I proceed to the get method of SyncRoot in the SortedList class.
SyncRoot:
public object SyncRoot
{
get
{
if (_syncRoot == null)
{
Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
}
return _syncRoot;
}
}
In theory, everything is fine. In fact, I went to check the source code of a much earlier version of the List class, and I saw that it had it the same way. Therefore, there shouldn't be any issues. Finally, I will show the Synchronized method.
Synchronized:
public ISortedList<T> Synchronized()
{
return new SynchronizedSortedList(this);
}
One obvious way to fix it would be as follows:
//Arrange
SortedList<int> list = new()
{
0
};
var synchronizedList = list.Synchronized();
int len = 2, maxIter = 1;
ParallelOptions options = new()
{
MaxDegreeOfParallelism = len
};
//Act
Parallel.For(0, len, options, i =>
{
for (int j = 0; j != maxIter; ++j)
{
lock(list.SyncRoot)
{
synchronizedList[0]++;
}
}
});
//Asserts
Assert.Equal(maxIter * len, list[0]);
In this way, it would work excellently, but it's not the way I want to do it because this Wrapper already has locks that protect the resource to prevent threads from accessing it and avoid concurrency. If someone could help me, I would greatly appreciate it because I've been dealing with this problem since yesterday and I can't think of much. Greetings.
It's a very long code, but I can provide the link to the integration test on GitHub, and here is the code for SortedList with the Wrapper.