You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Common/Common/Distributed/DistributedLockOps.cs

127 lines
3.3 KiB

2 years ago
using Connected.Caching;
using Connected.Entities;
using Connected.Entities.Storage;
using Connected.ServiceModel;
using Connected.Services;
namespace Common.Distributed;
internal sealed class DistributedLockOps
{
public class Lock : ServiceFunction<DistributedLockArgs, Guid>
{
static Lock()
{
SynchronizationState = new();
}
private static HashSet<string> SynchronizationState { get; }
public Lock(IStorageProvider storage, ICacheContext cache)
{
Cache = cache;
Storage = storage;
}
private ICacheContext Cache { get; }
private IStorageProvider Storage { get; }
private IDistributedLock Entity { get; set; }
protected override async Task<Guid> OnInvoke()
{
/*
* We must ensure that only one lock request is performed at a time for each entity. This is
* because we must guarantee that one and only one entry exist for each entity and its primary key.
*/
var key = $"{Arguments.Entity}.{Arguments.EntityId}".ToLowerInvariant();
/*
* If the hashset already holds the key it means someone else was faster than we and we are not able to
* perform a lock.
*/
if (!SynchronizationState.Add(key))
throw new SynchronizationLockException($"{SR.ValLock} ({key})");
try
{
Entity = await Storage.Open<DistributedLock>().Update(Arguments.AsEntity<DistributedLock>(State.New, new
{
Id = Guid.NewGuid(),
Expiration = DateTime.UtcNow.Add(Arguments.Duration ?? TimeSpan.FromSeconds(5))
}));
return Entity.Id;
}
finally
{
/*
* Free to remove the hash lock. Any subsequent lock requests will fail at data protection level.
*/
SynchronizationState.Remove(key);
}
}
protected override async Task OnCommitted()
{
/*
* Put the lock it the local cache.
*/
Cache.Set(DistributedLock.EntityKey, Entity.Id, Entity, TimeSpan.Zero);
await Task.CompletedTask;
}
}
public sealed class Unlock : ServiceAction<PrimaryKeyArgs<Guid>>
{
public Unlock(IStorageProvider storage, ICacheContext cache)
{
Storage = storage;
Cache = cache;
}
private IStorageProvider Storage { get; }
private ICacheContext Cache { get; }
protected override async Task OnInvoke()
{
await Storage.Open<DistributedLock>().Update(Arguments.AsEntity<DistributedLock>(State.Deleted));
}
protected override async Task OnCommitted()
{
await Cache.Remove(DistributedLock.EntityKey, Arguments.Id);
}
}
public sealed class Ping : ServiceAction<DistributedLockPingArgs>
{
public Ping(IStorageProvider storage, ICacheContext cache)
{
Storage = storage;
Cache = cache;
}
private IStorageProvider Storage { get; }
private ICacheContext Cache { get; }
protected override async Task OnInvoke()
{
var entity = await Cache.Get<IDistributedLock>(DistributedLock.EntityKey, Arguments.Id,
async (f) =>
{
return await (from dc in Storage.Open<DistributedLock>() where dc.Id == Arguments.Id select dc).AsEntity();
});
if (entity is not null)
{
await Storage.Open<DistributedLock>().Update(Arguments.AsEntity<DistributedLock>(State.Default, new
{
Expiration = DateTime.UtcNow.Add(Arguments.Duration ?? TimeSpan.FromSeconds(5))
}));
}
}
protected override async Task OnCommitted()
{
await Cache.Remove(DistributedLock.EntityKey, Arguments.Id);
}
}
}