3

I'm working on a caching layer on a web server on the serverside, using Azure Shared Caching, to reduce the amount of requests to the database and thus make stuff run faster (hopefully). What I'm getting stuck on is how the make the whole endevour thread safe. I don't seem to find a reliable and usable way to lock keys in the DataCache. What I'm missing is a way to preemtively lock a key before there's anything stored on it, so that I could add a value without risk of another thread trying to do the same thing at the same time.

I have been looking exclusively at pessimistic locking so far, since that's how thread safety makes the most sense to me, I want to be sure that the stuff I'm working on is locked.

I have understood that if I am to use pessimistic locking, I am responsible for only using the methods related to that. Mixing things would mess up the whole locking mechanisms (source: http://go4answers.webhost4life.com/Example/datacacheput-unlocking-key-77158.aspx).

So basicly I only have access to these methods:
value GetAndLock(key, out DataCacheLockHandle);
void PutAndUnlock(key, value, DataCacheLockHandle);
void Unlock(key, DataCacheLockHandle);

The trouble is, "GetAndLock" throws an exception if I try to get something that isn't already in the cache. At the same time, my only method for adding something to the cache is "PutAndUnlock", and that one can't be used unless I did a successful "GetAndUnlock".

In effect, it is impossible to add anything new to the cache, only thing that can be done is replacing things that are already there (which will be nothing).

So it seems to me that I am forced to use the optimistic "Put" in the case where "GetAndLock" throws the nothing there exception. According to what I've read, though, the optimistic "Put" destroys any existing lock achieved with "GetAndLock", so that would destroy the whole attempt at thread safety.

Example plan:
1. Try to GetAndLock
2. In case of nothing there exception: 
     - Put a dummy item on the key. 
     - GetAndLock again.
3. We have a lock, do computations, query database etc
4. PutAndUnlock the computed value


One of probably several ways it would screw up:
Thread1: Tries to GetAndLock, gets nothing there exception
Thread2: Tries to GetAndLock, gets nothing there exception
Thread1: Put a dummy item on the key
Thread1: GetAndLock again, lock achieved
Thread2: Put a dummy item on the key (destroying Thread1:s lock)
Thread2: GetAndLock again, lock achieved
Thread1: We think we have a lock, do computations, query database etc
Thread2: We have a lock, do computations, query database etc
Thread1: PutAndUnlock the computed value (will this throw an exception?)
Thread2: PutAndUnlock the computed value

Basicly the two threads could write different things to the same key at the same time, ignoring locks that they both think they have.

My only conclusion can be that the pessimistic locking of DataCache is feature incomplete and completely unusable. Am I missing something? Is there a way to solve this?

All I'm missing is a way to preemtively lock a key before there's anything stored on it.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • GetAndLock has parameter `forceLock`, where msdn says: If forceLock is true, key is locked irrespective of key-value pair presence in cache. Cant this helps you to lock key even when state does not exist for that key? – psulek Apr 23 '15 at 07:11

1 Answers1

0

Jonathan,

Have you considered this logic for adding things to the cache (please pardon my pseudo-code)?

public bool AddToCache(string key, object value) {

DataCache dc = _factory.GetDefaultCache();  
object currentVal = dc.Get(key);

if (currentVal == null) {
    dc.Put(key, value);
    currentVal = dc.GetAndLock(key);

    if (value == currentVal) {
        //handle this rare occurrence + unlock.
        return false;
    } else {
                   dc.Unlock(key);
            }
} else {
    currentVal = dc.GetAndLock(key);
    dc.PutAndUnlock (key, value);
}

return true;

}

SemanticZen
  • 1,141
  • 14
  • 21
  • I'm not sure I'm etirely following. Do you mean adding some code for handling the case when the lock was interrupted? – Jonatan Melin Mar 15 '13 at 09:29
  • Hmm, reading it again, I'm guessing you didn't mean that. The trouble with making a Get to check if the key isn't there, is that another thread might do the same while you are working, so you could enhd up with two paralell threads doing the Put. This Put can potentially destroy an aquired GetAndLock of the other thread. – Jonatan Melin Mar 15 '13 at 10:01
  • The idea is to: 1. do a get to see if it exists 2. if it doesn't exist add it 3. check it again (with a lock) 4. if the new value does NOT equal the value you just added then some other thread changed it between you checking if a value existed and before you put the lock on it --> so then you'll have to add some logic to handle that situation. – SemanticZen Mar 15 '13 at 15:33
  • I see. I believe that can also fail, but in a different way. Thread 1: Checks with the Get Thread 2: Checks with the Get Thread 1: Does the Put Thread 3: Checks with the Get Thread 1: Locks to check if value changed, it didn't Thread 1: Unlocks Thread 3: currentVal is not null, makes the normal case Lock Thread 2: Does the Put, destroying the Lock of thread 3 – Jonatan Melin Mar 19 '13 at 12:52
  • That said, I do have a plan now, one which I have mostly coded, but it isn't bug-free yet. I'll post about it when I think it is. The general idea is that I add a dummy item to the cache on application start, so that I have something that I know I can lock on, and then I use that as a kind of metalock for the entire cache to allow for checks and adds that go uninterrupted. – Jonatan Melin Mar 19 '13 at 12:54
  • make the method a static and lock – Sean Feb 06 '14 at 02:43