Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pause simultaneous REST calls until first one completes

We have a REST API method similar to:

List<item> GetItems(int AccountID)
{
    var x = getFromCache(AccountID);
    if(x==null)
    {
        x = getFromDatabase(AccountID);
        addToCache(AccountID, x);
    }
    return x;
}

This is a fairly costly method with some complicated DB calls, and we have a common situation where hundreds of users with the same AccountId will make the call almost simultaneously (they are all being notified with a broadcast).

In the method, we cache the result set for 10 seconds since a near-time result is fine for everyone making the request within that window. However, since they all make the call at the same time (again, for a specific AccountID) the cache is never populated up front, so everyone ends up making the database call.

So my question is, within the method, how can I pause all incoming requests for a specific accountId and make them all wait for the first result set to complete, so that the rest of the calls can use the cached result set?

I've read a little about Monitor.Pulse and Monitor.Lock but the implementation for a per-accountId lock kind of escapes me. Any help would be tremendously appreciated.

like image 306
Feech Avatar asked Oct 31 '22 17:10

Feech


1 Answers

You must lock on the same object for requests with the same AccountId but use different object for each individual AccountId. Here is example how to use Dictionary to keep track of locking object for individual AccountIds.

    Dictionary<int, Object> Locks = new Dictionary<int, object>();

    List<item> GetItems(int AccountID)
    {
        //Get different lock object for each AccountId
        Object LockForAccountId = GetLockObject(AccountID);

        //block subsequent threads until first thread fills the cache
        lock (LockForAccountId)
        {
            var x = getFromCache(AccountID);
            if (x == null)
            {
                x = getFromDatabase(AccountID);
                addToCache(AccountID, x);
            }
            return x;
        }
    }

    private Object GetLockObject(int AccountID)
    {
        Object LockForAccountId;

        //we must use global lock while accessing dictionary with locks to prevent multiple different lock objects to be created for the same AccountId
        lock (Locks)
        {
            if (!Locks.TryGetValue(AccountID, out LockForAccountId))
            {
                LockForAccountId = new Object();
                Locks[AccountID] = LockForAccountId;
            }
        }
        return LockForAccountId;
    }
like image 117
Ňuf Avatar answered Nov 15 '22 04:11

Ňuf