feat(fundamentals): add KeyedLockPool for concurrent scraping synchronization and update MQTT RPC handlers

This commit is contained in:
2026-08-24 21:35:43 +02:00
parent 8112598602
commit 600ccf299e
6 changed files with 136 additions and 69 deletions
@@ -0,0 +1,66 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace FinlyticFundamentals.Services;
public class KeyedLockPool
{
private readonly ConcurrentDictionary<string, RefCountedSemaphore> _semaphores = new(StringComparer.OrdinalIgnoreCase);
public async Task<IDisposable> LockAsync(string key, CancellationToken cancellationToken = default)
{
var item = _semaphores.AddOrUpdate(
key,
_ => new RefCountedSemaphore(),
(_, existing) =>
{
Interlocked.Increment(ref existing.RefCount);
return existing;
});
await item.Semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
return new Releaser(this, key, item);
}
private void Release(string key, RefCountedSemaphore item)
{
item.Semaphore.Release();
if (Interlocked.Decrement(ref item.RefCount) <= 0)
{
_semaphores.TryRemove(new KeyValuePair<string, RefCountedSemaphore>(key, item));
}
}
private sealed class RefCountedSemaphore
{
public int RefCount = 1;
public readonly SemaphoreSlim Semaphore = new(1, 1);
}
private sealed class Releaser : IDisposable
{
private readonly KeyedLockPool _pool;
private readonly string _key;
private readonly RefCountedSemaphore _item;
private bool _disposed;
public Releaser(KeyedLockPool pool, string key, RefCountedSemaphore item)
{
_pool = pool;
_key = key;
_item = item;
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_pool.Release(_key, _item);
}
}
}
}