0

Problem

A session value is not being retrieved in a Razor view and is causing faulty logic.

Environment

Redis sentinel with sentinel on web servers but only a single redis master and single redis slave. The redis connection string is pointing to both master and slave.

Code

In a controller before the view:

var fooLocal = fooMapper.Map(fooDbCall.GetFromDb(fooValue));

if (fooLocal != null)
{
    Session["FooSession"] = fooLocal.fooProperty;
}
else
{
    Session["FooSession"] = false;
}

In the view

@if (fooRazorVal == 123)
{
    // show some stuff
}
else if (!((bool?)Session["FooSession"] ?? false) && (fooRazorVal2 == 456))
{
    // show error message
}
else
{
    // show other stuff
}

Result

The error message is shown even when an account in question has been walked back through the code and database to verify it should not be false let alone null. Other session values are stored and retrieved fine or else you wouldn't even make it this far in my process.

Investigation

As I mentioned, all other code bits and the database have been verified. I added a logging class and there are lots of entries like so:

[Info]GetItemFromSessionStore => Session Id: ctps3urcqwm0tpezo5bbmqzj, Session provider object: 4686063 => Can not lock, Someone else has lock and lockId is 636901606595110722
[Info]GetItemFromSessionStore => Session Id: ctps3urcqwm0tpezo5bbmqzj, Session provider object: 26422156 => Lock taken with lockId: 636901606595110722
[Info]GetItemFromSessionStore => Session Id: ctps3urcqwm0tpezo5bbmqzj, Session provider object: 4686063 => Can not lock, Someone else has lock and lockId is 636901606595110722

However, given the sheer number of them, I'm wondering if this is actually an error or the RedisSessionStateProvider working as intended. I did see that it uses SETNX to acquire locks. Unfortunately, I'm not well versed enough in redis semantics to know if this is causing an issue.

I did see a note on the Redis docs about this being an old approach and to use RedLock instead. However, as I understand RedLock, a single master/single slave setup is not sufficient although it does support retries so maybe it would work anyway. I'm also curious if I should roll a simple custom provider that lets StackExhange's ConnectionMultiplexer work without extra locks or custom scripts and if I do need locks to use one of the C# libraries for RedLock.

Bigsby
  • 952
  • 6
  • 20

1 Answers1

1

By design, Redis keys are locked during update, you don't need to lock them. Indeed, Redis uses a single thread to process commands, so each operation is atomic. Other clients are blocked during the processing of a given command, that's why you mustn't perform queries with a long execution time and you are getting this error.

To prevent that one must implement distributed lock. Distributed locks are a very useful primitive in many environments where different processes must operate with shared resources in a mutually exclusive way.

Here are the different implementation for different language.

Implementations

Here are a few links to implementations already available that can be used for reference.

Redlock-rb (Ruby implementation). There is also a fork of Redlock-rb that adds a gem for easy distribution and perhaps more.

Redlock-py (Python implementation).

Aioredlock (Asyncio Python implementation).

Redlock-php (PHP implementation).

PHPRedisMutex (further PHP implementation)

Redsync.go (Go implementation).

Redisson (Java implementation).

Redis::DistLock (Perl implementation).

Redlock-cpp (C++ implementation).

Redlock-cs (C#/.NET implementation).

RedLock.net (C#/.NET implementation). Includes async and lock extension support.

ScarletLock (C# .NET implementation with configurable datastore)

node-redlock (NodeJS implementation). Includes support for lock extension.

See if this helps.

Mohit Verma
  • 5,140
  • 2
  • 12
  • 27
  • I can't change the Microsoft Redis provider to change how it does its locking. Is there anything else you can think of that would help alleviate this issue? – Bigsby Apr 11 '19 at 15:32