Couchbase.Extensions.Locks 1.0.0-beta

Prefix Reserved
This is a prerelease version of Couchbase.Extensions.Locks.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Couchbase.Extensions.Locks --version 1.0.0-beta                
NuGet\Install-Package Couchbase.Extensions.Locks -Version 1.0.0-beta                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Couchbase.Extensions.Locks" Version="1.0.0-beta" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Couchbase.Extensions.Locks --version 1.0.0-beta                
#r "nuget: Couchbase.Extensions.Locks, 1.0.0-beta"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Couchbase.Extensions.Locks as a Cake Addin
#addin nuget:?package=Couchbase.Extensions.Locks&version=1.0.0-beta&prerelease

// Install Couchbase.Extensions.Locks as a Cake Tool
#tool nuget:?package=Couchbase.Extensions.Locks&version=1.0.0-beta&prerelease                

Couchbase.Extensions.Locks

A system for managing distributed mutexs backed by Couchbase. This can prevent multiple simultaneous processes in separate application instances, which is useful for microservices or other horizontally scaled architectures.

Getting Started

Assuming you have an installation of Couchbase Server and Visual Studio VSCODE forthcoming), do the following:

  • Install the package from NuGet or build from source and add a reference.

Example Application

There is an example MVC application in the examples directory.

Requesting a Mutex

To request a Mutex, use the RequestMutex extension method on IBucket. This can be any type of bucket, but Memcached or Ephemeral would be the most efficient since they never write to disk.

The lock expiration controls when the lock will expire if it isn't explicitly released. The mutex may also be disposed to release the lock early. If the lock cannot be acquired because another process is holding the lock, a CouchbaseLockUnavailableException will be thrown.

using Couchbase.Extensions.DependencyInjection;
using Couchbase.Extensions.Locks;
using Microsoft.AspNetCore.Mvc;

public class MyController : Controller
{
    private readonly IBucketProvider _bucketProvider;

    public MyController(IBucketProvider bucketProvider)
    {
        _bucketProvider = bucketProvider;
    }

    public IActionResult Index()
    {
        var bucket = _bucketProvider.GetBucket("default");

        try {
            using (var mutex = await bucket.RequestMutexAsync("my-lock-name", TimeSpan.FromSeconds(15)))
            {
                // This lock will be held for the shorter of the using statement lifespan or 15 seconds
            }
        }
        catch (CouchbaseLockUnavailableException ex)
        {
            // This exception indicates the lock is already held by another process
        }
    }
}

Renewing the lock

If your process is long-running, the lock may be renewed before it expires. This will succeed so long as the lock hasn't been released and acquired by another process. If the lock already expired but is still released, it will be automatically reacquired.

using Couchbase.Extensions.DependencyInjection;
using Couchbase.Extensions.Locks;
using Microsoft.AspNetCore.Mvc;

public class MyController : Controller
{
    private readonly IBucketProvider _bucketProvider;

    public MyController(IBucketProvider bucketProvider)
    {
        _bucketProvider = bucketProvider;
    }

    public IActionResult Index()
    {
        var bucket = _bucketProvider.GetBucket("default");

        try {
            using (var mutex = await bucket.RequestMutexAsync("my-lock-name", TimeSpan.FromSeconds(15)))
            {
                while (true)
                {
                    // Do some work here

                    // This lock will be held until the end of the using statement,
                    // unless one of the loop iterations takes longer than 15 seconds.

                    await mutex.RenewAsync(TimeSpan.FromSeconds(15));
                }
            }
        }
        catch (CouchbaseLockUnavailableException ex)
        {
            // This exception indicates the lock is already held by another process
        }
    }
}

Automatically Renewing Locks

In many cases, it may be desirable for locks to automatically renew themselves until they expire, allowing a shorter expiration time. This means that if the process crashes the lock is freed soon, but the lock can constantly be refreshed so long as the process is still running.

This feature is enabled by calling AutoRenew on the mutex after it is acquired. The first parameter is how often to renew the lock, and should be set to significantly shorter than the original lock expiration.

The second parameter is the maximum lifetime of the lock. This is a safety mechanism in case the call to Dispose is somehow missed. Auto renewal will cease after this amount of time.

using Couchbase.Extensions.DependencyInjection;
using Couchbase.Extensions.Locks;
using Microsoft.AspNetCore.Mvc;

public class MyController : Controller
{
    private readonly IBucketProvider _bucketProvider;

    public MyController(IBucketProvider bucketProvider)
    {
        _bucketProvider = bucketProvider;
    }

    public IActionResult Index()
    {
        var bucket = _bucketProvider.GetBucket("default");

        try {
            using (var mutex = await bucket.RequestMutexAsync("my-lock-name", TimeSpan.FromSeconds(10)))
            {
                mutex.AutoRenew(TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(1));

                while (true)
                {
                    // Do some work here

                    // This lock will be held until the end of the using statement,
                    // so long as the total loop time is less than one minute
                }
            }
        }
        catch (CouchbaseLockUnavailableException ex)
        {
            // This exception indicates the lock is already held by another process
        }
    }
}

Retrying Locks

By design, requesting a mutex is only attempted once. If the lock is unavailable, CouchbaseLockUnavailableException is thrown immediately. This allows the consumer to implement advanced retry logic. We recommend using Polly or a similar library. This allows waits, exponential backoffs, circuit breakers, bulkhead isolation, and other advanced logic.

using Couchbase.Extensions.DependencyInjection;
using Couchbase.Extensions.Locks;
using Microsoft.AspNetCore.Mvc;
using Polly;

public class MyController : Controller
{
    // Retry up to 10 times, waiting one second between attempts
    private static readonly Policy LockPolicy =
        Policy.Handle<CouchbaseLockUnavailableException>()
            .WaitAndRetryAsync(10, _ => TimeSpan.FromSeconds(1));

    private readonly IBucketProvider _bucketProvider;

    public MyController(IBucketProvider bucketProvider)
    {
        _bucketProvider = bucketProvider;
    }

    public IActionResult Index()
    {
        var bucket = _bucketProvider.GetBucket("default");

        try {
            // Wrapping the call in LockPolicy.ExecuteAsync applies the wait and retry logic
            // for any CouchbaseLockUnavailableException.  All other exceptions throw immediately.
            using (var mutex = await LockPolicy.ExecuteAsync(
                () => bucket.RequestMutexAsync("my-lock-name", TimeSpan.FromSeconds(10))))
            {
                // Do work here
            }
        }
        catch (CouchbaseLockUnavailableException ex)
        {
            // This exception indicates the lock is already held by another process,
            // for 10 consequetive lock attempts
        }
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Couchbase.Extensions.Locks:

Package Downloads
Whipstaff.Couchbase

Re-usable logic for working with Couchbase.

Hyperbee.Migrations.Providers.Couchbase

Hyperbee Migrations Postgres Provider adds Couchbase support to Hyperbee Migrations.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 186,582 11/18/2020
1.0.0 103,475 9/18/2019
1.0.0-beta 8,415 11/2/2018