Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

MemoryCacheLocker.cs

using Microsoft.Extensions.Caching.Memory;

namespace Nop.Core.Caching;

/// 
/// A distributed cache manager that locks the acquisition task
/// 
public partial class MemoryCacheLocker : ILocker
{
    #region Fields

    protected readonly IMemoryCache _memoryCache;

    #endregion

    #region Ctor

    public MemoryCacheLocker(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    #endregion

    #region Utilities

    /// 
    /// Run action
    /// 
    /// The key of the background task
    /// The time after which the lock will automatically be expired
    /// The action to perform
    /// A CancellationTokenSource for manually canceling the task
    /// 
    protected virtual async Task RunAsync(string key, TimeSpan? expirationTime, Func action, CancellationTokenSource cancellationTokenSource = default)
    {
        var started = false;

        try
        {
            var tokenSource = _memoryCache.GetOrCreate(key, entry => new Lazy(() =>
            {
                entry.AbsoluteExpirationRelativeToNow = expirationTime;
                entry.SetPriority(CacheItemPriority.NeverRemove);
                started = true;
                return cancellationTokenSource ?? new CancellationTokenSource();
            }, true))?.Value;

            if (tokenSource != null && started)
                await action(tokenSource.Token);
        }
        catch (OperationCanceledException) { }
        finally
        {
            if (started)
                _memoryCache.Remove(key);
        }

        return started;
    }

    #endregion

    #region Methods

    /// 
    /// Performs some asynchronous task with exclusive lock
    /// 
    /// The key we are locking on
    /// The time after which the lock will automatically be expired
    /// Asynchronous task to be performed with locking
    /// A task that resolves true if lock was acquired and action was performed; otherwise false
    public async Task PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func action)
    {
        return await RunAsync(resource, expirationTime, _ => action());
    }

    /// 
    /// Starts a background task with "heartbeat": a status flag that will be periodically updated to signal to
    /// others that the task is running and stop them from starting the same task.
    /// 
    /// The key of the background task
    /// The time after which the heartbeat key will automatically be expired. Should be longer than 
    /// The interval at which to update the heartbeat, if required by the implementation
    /// Asynchronous background task to be performed
    /// A CancellationTokenSource for manually canceling the task
    /// A task that resolves true if lock was acquired and action was performed; otherwise false
    public async Task RunWithHeartbeatAsync(string key, TimeSpan expirationTime, TimeSpan heartbeatInterval, Func action, CancellationTokenSource cancellationTokenSource = default)
    {
        // We ignore expirationTime and heartbeatInterval here, as the cache is not shared with other instances,
        // and will be cleared on system failure anyway. The task is guaranteed to still be running as long as it is in the cache.
        await RunAsync(key, null, action, cancellationTokenSource);
    }

    /// 
    /// Tries to cancel a background task by flagging it for cancellation on the next heartbeat.
    /// 
    /// The task's key
    /// The time after which the task will be considered stopped due to system shutdown or other causes,
    /// even if not explicitly canceled.
    /// A task that represents requesting cancellation of the task. Note that the completion of this task does not
    /// necessarily imply that the task has been canceled, only that cancellation has been requested.
    public Task CancelTaskAsync(string key, TimeSpan expirationTime)
    {
        if (_memoryCache.TryGetValue(key, out Lazy tokenSource))
            tokenSource.Value.Cancel();

        return Task.CompletedTask;
    }

    /// 
    /// Check if a background task is running.
    /// 
    /// The task's key
    /// A task that resolves to true if the background task is running; otherwise false
    public Task IsTaskRunningAsync(string key)
    {
        return Task.FromResult(_memoryCache.TryGetValue(key, out _));
    }

    #endregion
}