none
Thread-safety while reading bool property without locks RRS feed

  • Question

  • I've been trying to track down an extremely weird issue that occurs very rarely and takes a long time to manifest. This code pattern seemed to stand out, and I wanted to ensure this is thread-safe. A simplified form of the pattern here shows a TestClassManager class that manages leasing for TestClass objects. A TestClass object will get leased, used, and released. Once a TestClass is released, it will not be modified/used by any other thread further.

    class Program { public static void Main(string[] args) { var tasks = new List<Task>(); var testClassManager = new TestClassManager(); tasks.Add(Task.Factory.StartNew(() => TestersOperationLoop(testClassManager), TaskCreationOptions.LongRunning)); tasks.Add(Task.Factory.StartNew(() => ClearTestersLoop(testClassManager), TaskCreationOptions.LongRunning)); Task.WaitAll(tasks.ToArray()); } public class TestClassManager { private readonly object _testerCollectionLock = new object(); private readonly Dictionary<long, TestClass> _leasedTesters = new Dictionary<long, TestClass>(); private readonly Dictionary<long, TestClass> _releasedTesters = new Dictionary<long, TestClass>(); public TestClass LeaseTester() { lock (_testerCollectionLock) { var tester = new TestClass(); _leasedTesters.Add(tester.Id, tester); _releasedTesters.Remove(tester.Id); return tester; } } public void ReleaseTester(long id) { lock (_testerCollectionLock) { var tester = _leasedTesters[id]; _leasedTesters.Remove(tester.Id); _releasedTesters.Add(tester.Id, tester); } } public void Clear() { lock (_testerCollectionLock) { foreach (var tester in _releasedTesters) {

    // this exception is never thrown, but is this possible? if (!tester.Value.IsChanged) { throw new InvalidOperationException("Is this even possible!?!"); } } var clearCount = _releasedTesters.Count; _releasedTesters.Clear(); } } } public class TestClass { private static long _count; private long _id; private bool _status; private readonly object _lockObject = new object(); public TestClass() { Id = Interlocked.Increment(ref _count); }

    // reading status without the lock public bool IsChanged { get { return _status; } } public long Id { get => _id; set => _id = value; } public void SetStatusToTrue() { lock (_lockObject) { _status = true; } } } public static void TestersOperationLoop(TestClassManager testClassManager) { while (true) { var tester = testClassManager.LeaseTester(); tester.SetStatusToTrue(); testClassManager.ReleaseTester(tester.Id); } } public static void ClearTestersLoop(TestClassManager testClassManager) { while (true) { testClassManager.Clear(); } } }


    Is the check for TestClass.IsChanged property from within the TestClassManager.Clear method thread-safe? I don't ever see the InvalidOperationException, but is that possible? If it is, that would explain my issue.

    Irrespective of this, I am going to lock the read anyway to follow commonly suggested pattern of locked reads if locked writes. But I wanted to get some closure on understanding if this would actually cause an issue, since this would explain that extremely weird rare bug!! that keeps me awake at night.

    Thanks!



    • Edited by GDinak Thursday, February 6, 2020 6:17 PM Clarification
    Thursday, February 6, 2020 12:45 AM

All replies

  • Hi GDinak,

    Thank you for posting here.

    I make a test based on your code, and find that InvalidOperationException never be thrown.

    Even I annotate 'TestersOperationLoop':

                //tasks.Add(Task.Factory.StartNew(() => TestersOperationLoop(testClassManager), TaskCreationOptions.LongRunning));
                tasks.Add(Task.Factory.StartNew(() => ClearTestersLoop(testClassManager), TaskCreationOptions.LongRunning));

    Since '_releasedTesters' has no values, code in for-each statement never be executed.

    I note that 'TestClass' is created in 'LeaseTester' method and '_status' will be set 'True' after executing 'SetStatusToTrue()' method.

    The process is thread-safe, so '_status' in '_releasedTesters' is always 'True', and InvalidOperationException will never be thrown.

    Hope it can help you.

    Best Regards,

    Xingyu Zhao

     

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, February 7, 2020 7:29 AM
    Moderator