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/Documents/DocumentLocker.cs

97 lines
2.2 KiB

using Common.Distributed;
using Connected;
using Connected.Interop;
using Connected.ServiceModel;
using Connected.Threading;
namespace Common.Documents;
internal sealed class DocumentLocker<TDocument, TPrimaryKey> : IDocumentLocker<TDocument, TPrimaryKey>
where TDocument : IDocument<TPrimaryKey>
where TPrimaryKey : notnull
{
public event EventHandler? Expired;
public DocumentLocker(IDistributedLockService locking)
{
Locking = locking;
Cancel = new();
}
private bool IsDisposed { get; set; }
public Guid Key { get; private set; }
private IDistributedLockService Locking { get; }
public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(2);
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(1500);
public TimeSpan Lifetime { get; set; } = TimeSpan.FromSeconds(30);
private CancellationTokenSource Cancel { get; }
public async Task Lock(TDocument document)
{
var id = TypeConversion.Convert<string>(document.Id);
if (string.IsNullOrEmpty(id))
throw new NullReferenceException(nameof(document.Id));
/*
* Obtain the lock on the document to ensure no other process
* will change it while we are updating the stock. If the lock
* could not be obtained the exception will be thrown.
*/
Key = await Locking.Lock(new DistributedLockArgs
{
Duration = LockTimeout,
Entity = typeof(TDocument).Name,
EntityId = id
});
/*
* Make sure we don't get outdated by using the scheduled
* task which will ping the lock before it expires.
*/
using var timeout = new ScheduledTask(async () =>
{
await Locking.Ping(new DistributedLockPingArgs
{
Id = Key,
Duration = LockTimeout
});
}, async () =>
{
await Unlock();
Expired?.Invoke(this, EventArgs.Empty);
}, Timeout, Lifetime, Cancel.Token);
timeout.Start();
}
public async Task Unlock()
{
/*
* Finally release the lock on the document so it becomes
* updatable again.
*/
await Locking.Unlock(new PrimaryKeyArgs<Guid> { Id = Key });
}
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
AsyncUtils.RunSync(Unlock);
Cancel.Cancel();
Cancel.Dispose();
}
IsDisposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}