|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|