Skip to content

Single-node in-memory implementation #223

Open

Description

@srogovtsev

Consider the following scenario: we have a system that might run in a distributed mode and in a single-node mode. There are routines that require cross-node synchronization, so we use distributed locks. We use DI and IDistributedLockProvider to separate ourselves from the implementation details (and use different stores depending on the deployment. But when we deploy the same system in a single-node, we don't need any kind of backing store, and I would assume that the same functionality can be reasonable implemented simply in-memory, based on existing .net synchronization primitives, and just injected instead (e.g., same way as AddDistributedMemoryCache implements in-memory cache for IDistributedCache).

But I would suppose that this is a very obvious idea, and it's either that I simply missed this implementation, or there's some hidden complexity/issue that I miss, and it would be recommended to use a backing store even on single-node installations.

Will be very glad to get any pointers.

Activity

  1. madelson commented on Jul 11, 2024

    @madelson
    Owner

    Hi @srogovtsev thanks for your interest in the library.

    This use-case makes sense and is something I’ve considered in the past. As far as why I haven’t built it (yet):

    1. I didn’t have a real use-case outside of unit tests, and for that there are other reasonable options such as…
    2. File-based and wait handle-based locks, which are effectively local machine scoped. Of course, the latter is mostly windows-only.
    3. The implementation is simple but not entirely trivial because I’d want to retain name-based identity, meaning that 2 different lock objects with the same name need to synchronize with each other (via underlying static data structures that can’t leak memory). Another challenge is the lack of (last time I checked) a native async RW lock implementation, which id want to support.

    I would call this “process-scoped synchronization”.

    If this is something you’d be interested in contributing, let me know and I can offer some guidance.

  2. mingyangzhu commented on Jul 15, 2024

    @mingyangzhu

    Very useful for monolithic applications that do not want to introduce external components

  3. netdragonboberb commented on Jun 29, 2025

    @netdragonboberb

    For single node situations like local dev and unit tests, you can do this (non-async bc it'd be in Program.cs) using TestContainers.Redis:

        var redisContainer = new RedisBuilder()
            .WithImage("redis:7.0") // Pin to a specific Redis version for stability
            .Build();
        Task.Run(async () => await redisContainer.StartAsync()).Wait();
        // Connect to Redis using the container's connection string
        var redisConnection = ConnectionMultiplexer.Connect(redisContainer.GetConnectionString());
        // Get the IDatabase instance
        return redisConnection.GetDatabase();
    
  4. JohnGalt1717 commented on Jul 1, 2025

    @JohnGalt1717

    The issue with redis like this is that it adds massive overhead to your testing, because you can't actually test anything if you use a shared instance of redis so you're spinning containers for every single test.

    Also, it's not possible to use Garnet in memory because:

    
     One or more errors occurred. (ERR This instance has Lua scripting support disabled)
           ---> StackExchange.Redis.RedisServerException: ERR This instance has Lua scripting support disabled
             at StackExchange.Redis.RedisDatabase.ScriptEvaluateAsync(String script, RedisKey[] keys, RedisValue[] values, CommandFlags flags) in /_/src/StackExchange.Redis/RedisDatabase.cs:line 1645
             at Medallion.Threading.Redis.RedLock.RedLockRelease.ReleaseAsync() in /_/src/DistributedLock.Redis/RedLock/RedLockRelease.cs:line 57
             --- End of inner exception stack trace ---
             at Medallion.Threading.Redis.RedLock.RedLockRelease.ReleaseAsync() in /_/src/DistributedLock.Redis/RedLock/RedLockRelease.cs:line 66
             at Medallion.Threading.Redis.RedLock.RedLockHandle.DisposeAsync() in /_/src/DistributedLock.Redis/RedLock/RedLockHandle.cs:line 46
             at Shared.Locking.DistributedSynchronizationHandle`1.DisposeAsync() in E:\Repos\ProjectFulcrum\Api\Shared\Locking\IDistributedSynchronizationHandle.cs:line 20 
    

    Which was removed from IDistributedCache functionality because Redis itself is depreciating it so nothing else supports it that is new for replacements.

  5. ntark commented on Oct 20, 2025

    @ntark

    Another challenge is the lack of (last time I checked) a native async RW lock implementation, which id want to support.

    @madelson Sorry if its ignorant, but wouldn't SemaphoreSlim work? It has WaitAsync and supports timeouts.

  6. madelson commented on Oct 20, 2025

    @madelson
    Owner

    @ntark SemaphoreSlim gives you an easy mutex (lock) or of course a semaphore.

    However, I don’t think it gives us reader-writer lock semantics out of the box. Let me know if I’m missing something.

  7. ntark commented on Oct 21, 2025

    @ntark

    @madelson Yep I didn't correctly get the RW abbreviation. While not out of the box, there is an option of configuring to have N Readers and running Wait/WaitAsync N times on Write Lock, although it does seem janky and non-optimal.

  8. hoerup commented on Oct 21, 2025

    @hoerup
  9. madelson commented on Oct 21, 2025

    @madelson
    Owner

    @ntark yeah you can do that but it has a few issues:

    • no upgradable write functionality
    • does not support “any number of readers” (and if you make the semaphore count huge then taking the write lock takes forever
    • if 2 threads take the write lock at the same time each may get some semaphore tickets, resulting in a deadlock

    @hoerup yeah there’s even ReaderWriterLockSlim but last I checked it doesn’t support async

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions