Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Locking per ASP.NET session

Tags:

c#

asp.net

OK some background. I have something similar to this:

class ConnectionFactory 
{
    public IConnection Connect()
    {
        if (User.IsAuthenticated) {
            return InternalConnect(User.Username, null);
        }
        return null;
    }
    public IConnection Connect(string username, string password)
    {
        return InternalConnect(username, password);
    }
    private IConnection InternalConnect(string username, string password)
    {
         IConnection connection;
         var cacheKey = Session[CacheKeySessionKey] as string;

         if (!string.IsNullOrEmpty(cacheKey)) {
            connection = HttpCache[cacheKey] as IConnection;   
         }

         if (!IsGoodConnection(connection) {
            connection = MakeConnection(username, password); // very costly
            cacheKey = Session[CacheKeySessionKey] = // some key
            HttpCache[cacheKey] = connection;
         }

         return connection;
    }
    private bool IsGoodConnection(IConnection conn)
    {
        return conn != null && conn.IsConnected;
    }
}

I'm currently running into a concurrency problem where that Connect() is being called multiple times and creating multiple IConnections per request. I only need one. It is being injected using an IoC container into various instances. MakeConnnection is very costly as it spins up a WCF channel.

My question is: How can I lock the InternalConnect calls per session? I don't think locking per request is the right way to go as multiple requests can happen per user. I certainly don't want to lock for every call, as this will give bad performance.

I think that doing this is a bad idea:

lock(Session.SessionID)
{
   // Implementation of InternalConnect
}

Note: The username and password overload is what I call only on login.

like image 742
Daniel A. White Avatar asked Feb 16 '12 13:02

Daniel A. White


2 Answers

This is just untested code, from the top of my head, but it may work?

// globally declare a map of session id to mutexes
static ConcurrentDictionary<string, object> mutexMap = new ConcurrentDictionary();

// now you can aquire a lock per session as follows
object mutex = mutexMap.GetOrAdd(session.SessionId, key => new object());
lock(mutex) 
{
    // Do stuff with the connection
}

You would need to find a way to clear old sessions out of the mutexMap but that shouldn't be too difficult.

like image 196
Andrew Skirrow Avatar answered Nov 05 '22 07:11

Andrew Skirrow


This is a utility class I use, I can't remember how much of it wrote but I think it is based on code by Stephen Cleary.

It handles async (due to the Nito NuGet package), is concurrent (can handle multiple callers) and tidies up the lock afterwards (the finally clause). You just need to give it a unique key and the function to execute.

using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

public static class ThingLocker
{
    private static readonly ConcurrentDictionary<string, AsyncLock> locks = new ConcurrentDictionary<string, AsyncLock>();

    public static async Task ExecuteLockedFunctionAsync(string key, Func<Task> func)
    {
        AsyncLock mutex = null;

        try
        {
            mutex = locks.GetOrAdd(key, new AsyncLock());

            using (await mutex.LockAsync())
            {
                await func();
            }
        }
        finally
        {
            if (mutex != null)
            {
                locks.TryRemove(key, out var removedValue);
            }
        }
    }
}

You'd use it like this;

await ThingLocker.ExecuteLockedFunctionAsync("user id etc.", () => { DoThingHere(); } );

You could pass it the address of an async function instead which would make it look tidier.

like image 1
Ryan O'Neill Avatar answered Nov 05 '22 07:11

Ryan O'Neill