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.
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.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With