0

I'm facing a little problem with locks in C# (but no matter the language, it's more an algorithmic question).

When I enter in a method, I take a read lock on an object. But if the given object is null (not initialized), I need to initialize it. So, I need to get a write lock on it. But, the problem is that I am already in a read lock zone.

Ex:

 public string LocalUid
        {
            get
            {
                        using (new ReadLock(MongoDBConnector.adminLock))
                        { 
                            MongoClient mongo = MongoDBConnector.GetAdminClient();
                           // Do something...
                        }
                    }
                    return localUid;
                }
                return null;
            }
        }

and the GetAdminClient method is:

public static MongoClient GetAdminClient()
        {
            if (adminClient == null)
            {
                using (new WriteLock(adminLock))
                {
                    if (adminClient == null) // Double check locking pattern
                    {
                        adminClient = CreateAdminClient();
                    }
                }
            }
            return adminClient;
        }

So we clearly see that the writelock is asked in a readlocked zone :(

Any idea / best practice for this case ?

hiveship
  • 308
  • 2
  • 7
  • 21
  • 2
    An upgradable read lock is not uncommon in the read-write lock implementations. For example C# does have that in its built-in [`ReaderWriterLockSlim`](https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.enterupgradeablereadlock(v=vs.110).aspx) so perhaps what you're using has something similar? – apokryfos Feb 13 '18 at 09:34
  • Possible duplicate of [Why are locks performed on separate objects?](https://stackoverflow.com/questions/1873201/why-are-locks-performed-on-separate-objects) – mjwills Feb 13 '18 at 09:34
  • 1
    If you are trying to ensure that multiple threads don't create an object in parallel, consider using `Lazy` or `LazyWithNoExceptionCaching`. https://stackoverflow.com/a/42567351/34092 _Then maybe lock on `Lazy.Value`._ – mjwills Feb 13 '18 at 09:37
  • 2
    Are you sure you need read\write locks at all in this case? Seems to me `GetAdminClient` can just use regular `lock`. – Evk Feb 13 '18 at 09:41
  • If your trivial property reads are doing thead-safe lazy initialization of database objects behind the scenes, you're doing it wrong. Move initialization to limited, well-known spots rather than "anywhere" and you save yourself a metric ton of headaches. – Jeroen Mostert Feb 13 '18 at 09:47

1 Answers1

1

This is a common pattern known as lock escalation. You have to be very careful to avoid deadlocks caused by lock escalation:

Thread A acquires read lock on Resource X.
Thread B acquires read lock on Resource X (this is allowed because Thread A only has a read lock).
Thread A wants to escalate its read lock to a write lock on Resource X, but it has to wait for Thread B to first release its read lock.
Thread B wants to escalate its read lock to a write lock on Resource X, but it has to wait for Thread A to first release its read lock.
Threads A and B are now deadlocked.

This type of deadlock can be avoided by taking a write lock to begin with, i.e., take a write lock when reading if the result of the read may require you to later take a write lock.

Polyfun
  • 9,479
  • 4
  • 31
  • 39