2020-07-21 23:14:45 +00:00
|
|
|
using System.Collections.Concurrent;
|
2020-05-05 14:03:46 +00:00
|
|
|
|
|
|
|
using NodaTime;
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
namespace PluralKit.Core;
|
|
|
|
|
|
|
|
public class HandlerQueue<T>
|
2020-05-05 14:03:46 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
private readonly ConcurrentDictionary<long, HandlerEntry> _handlers = new();
|
|
|
|
private long _seq;
|
|
|
|
|
|
|
|
public async Task<T> WaitFor(Func<T, bool> predicate, Duration? timeout = null, CancellationToken ct = default)
|
2020-05-05 14:03:46 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
var timeoutTask = Task.Delay(timeout?.ToTimeSpan() ?? TimeSpan.FromMilliseconds(-1), ct);
|
|
|
|
var tcs = new TaskCompletionSource<T>();
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
ValueTask Handler(T e)
|
2020-05-05 14:03:46 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
tcs.SetResult(e);
|
|
|
|
return default;
|
|
|
|
}
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var entry = new HandlerEntry { Predicate = predicate, Handler = Handler };
|
|
|
|
_handlers[Interlocked.Increment(ref _seq)] = entry;
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
// Wait for either the event task or the timeout task
|
|
|
|
// If the timeout task finishes first, raise, otherwise pass event through
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var theTask = await Task.WhenAny(timeoutTask, tcs.Task);
|
|
|
|
if (theTask == timeoutTask)
|
|
|
|
throw new TimeoutException();
|
2020-05-05 14:03:46 +00:00
|
|
|
}
|
2021-11-27 02:10:56 +00:00
|
|
|
finally
|
2020-05-05 14:03:46 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
entry.Remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
return await tcs.Task;
|
|
|
|
}
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public async Task<bool> TryHandle(T evt)
|
|
|
|
{
|
|
|
|
// First pass to clean up dead handlers
|
|
|
|
foreach (var (k, entry) in _handlers)
|
|
|
|
if (!entry.Alive)
|
|
|
|
_handlers.TryRemove(k, out _);
|
|
|
|
|
|
|
|
// Now iterate and try handling until we find a good one
|
|
|
|
var now = SystemClock.Instance.GetCurrentInstant();
|
|
|
|
foreach (var (_, entry) in _handlers)
|
|
|
|
if (entry.Expiry < now)
|
|
|
|
{
|
|
|
|
entry.Alive = false;
|
|
|
|
}
|
|
|
|
else if (entry.Alive && entry.Predicate(evt))
|
2020-05-05 14:03:46 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
await entry.Handler(evt);
|
|
|
|
entry.Alive = false;
|
|
|
|
return true;
|
2020-05-05 14:03:46 +00:00
|
|
|
}
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public class HandlerEntry
|
|
|
|
{
|
|
|
|
internal bool Alive = true;
|
|
|
|
internal Instant Expiry = SystemClock.Instance.GetCurrentInstant() + Duration.FromMinutes(30);
|
|
|
|
internal Func<T, ValueTask> Handler;
|
|
|
|
internal Func<T, bool> Predicate;
|
2020-05-05 14:03:46 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public void Remove() => Alive = false;
|
2020-05-05 14:03:46 +00:00
|
|
|
}
|
|
|
|
}
|