Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Multithreaded Access to Shared Login Session

I'm working on a Web API application which connects to a backend system via an API. One challenge of working with the API is that it requires a remote session to be maintained that is shared between all threads/requests to the Web API. The session expires every few hours and needs to be "refreshed" via a login.

A simplified version of my current implementation is below:

private static Object loginLock = new Object();

if(!Api.IsLoggedIn) 
{
    lock(loginLock)
    {
        if(!Api.IsLoggedIn)
        {
            Api.Login();
        }
    }
}

// Do stuff with the API

Under high concurrent load, when a login is required, threads get stacked up at the lock and are let through one at a time upon a successful login which is causing a performance bottleneck.

What I'm looking for is a way to block all threads when a login is required, but let them all through upon successful login.

Is there a better pattern for addressing this issue? Googling seems to indicate that ReaderWriterLockSlim or Monitor Wait/Pulse/PulseAll might be better candidates than a standard lock.

like image 581
WayneC Avatar asked Oct 18 '22 21:10

WayneC


2 Answers

It's an unusual problem, and I'm not aware of anything built in that specifically addresses this.

Bearing in mind that I've knocked this up in a few minutes, and so I'd definitely advise not using this until plenty of people have had a chance to look at it and point out its flaws, this is what I thought of:

private Task _loginLock = null;

public void DoLoggedInCheck()
{
    if (!Api.IsLoggedIn)
    {
        var tcs = new TaskCompletionSource<int>();
        var tsk = tcs.Task;
        var result = Interlocked.CompareExchange(ref _loginLock, tsk, null);
        if (result == null)
        {
            if (!Api.IsLoggedIn)
            {
                Api.Login();
            }
            Interlocked.Exchange(ref _loginLock, null);
            tcs.SetResult(1);
        }
        else
        {
            result.Wait();
        }
    }
}

The logic being that, out of all of the threads that spot that a login is required, they all compete (via the CompareExchange) to be the one to volunteer to fix the problem. One of them wins and does the task, the remainder just wait for the winner to signal their success.

There's still a small amount of raciness here, but it should be rare.

like image 68
Damien_The_Unbeliever Avatar answered Nov 03 '22 00:11

Damien_The_Unbeliever


If you want to resolve it only using worker threads I don't see any other ways, there is a critical section and this is nature of critical section that only 1 thread can pass it at a time. Completely other approach would be do delegate handling of critical section to separate thread. I am not sure that it will be more performant (most probably it will be much slower) but once Api is logged in there will be no traffic jam.

        private static AutoResetEvent requestLogin = new AutoResetEvent();
        private static ManualResetEvent responseLogin = new ManualResetEvent();

        //Worker thread:

        if(!Api.IsLoggedIn) 
        {
            requestLogin.Set();
            responseLogin.WaitOne();
        }

        //Login thread

        requestLogin.WaitOne();
        if(!Api.IsLoggedIn)
        {
            Api.Login();
        }
        responseLogin.Set();
like image 22
Andrey Avatar answered Nov 03 '22 00:11

Andrey