Fix crash if DelayedTaskScheduler has too big timestamp

This commit is contained in:
nicolas.dorier
2025-04-09 00:03:43 +09:00
parent 462e6c58d9
commit 5df2ffe689
2 changed files with 31 additions and 10 deletions

View File

@@ -3030,7 +3030,9 @@ namespace BTCPayServer.Tests
}); });
var invoiceRepo = tester.PayTester.GetService<InvoiceRepository>(); var invoiceRepo = tester.PayTester.GetService<InvoiceRepository>();
var invoice = await invoiceRepo.GetInvoice("Q7RqoHLngK9svM4MgRyi9y"); var invoice = await invoiceRepo.GetInvoice("Q7RqoHLngK9svM4MgRyi9y");
#pragma warning disable CS0618 // Type or member is obsolete
var p = invoice.Payments.First(p => p.Id == "26c879f3d27a894a62f8730c84205ac9dec38b7bbc0a11ccc0c196d1259b25aa-1"); var p = invoice.Payments.First(p => p.Id == "26c879f3d27a894a62f8730c84205ac9dec38b7bbc0a11ccc0c196d1259b25aa-1");
#pragma warning restore CS0618 // Type or member is obsolete
var details = p.GetDetails<BitcoinLikePaymentData>(handlers.GetBitcoinHandler("BTC")); var details = p.GetDetails<BitcoinLikePaymentData>(handlers.GetBitcoinHandler("BTC"));
Assert.Equal("6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d", details.AssetId.ToString()); Assert.Equal("6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d", details.AssetId.ToString());
} }

View File

@@ -15,12 +15,25 @@ namespace BTCPayServer.Services
{ {
Logger = logger; Logger = logger;
} }
record class TimerState(string Key, Func<Task> Act); record class TimerState(string Key, Func<Task> Act, DateTimeOffset ExecuteAt)
{
internal TimeSpan NextWait()
{
var due = ExecuteAt - DateTimeOffset.UtcNow;
if (due < TimeSpan.Zero)
due = TimeSpan.Zero;
// Max timer needed, else dotnet crash
if (due > MaxTimer)
due = MaxTimer;
return due;
}
}
private readonly Dictionary<string, Timer> _timers = new(); private readonly Dictionary<string, Timer> _timers = new();
bool disposed = false; bool disposed = false;
public ILogger<DelayedTaskScheduler> Logger { get; } public ILogger<DelayedTaskScheduler> Logger { get; }
static TimeSpan MaxTimer = TimeSpan.FromMilliseconds(4294967294);
public void Schedule(string key, DateTimeOffset executeAt, Func<Task> act) public void Schedule(string key, DateTimeOffset executeAt, Func<Task> act)
{ {
lock (_timers) lock (_timers)
@@ -33,23 +46,26 @@ namespace BTCPayServer.Services
existing.Dispose(); existing.Dispose();
_timers.Remove(key); _timers.Remove(key);
} }
var due = executeAt - DateTimeOffset.UtcNow;
if (due < TimeSpan.Zero) var state = new TimerState(key, act, executeAt);
due = TimeSpan.Zero; var timer = new Timer(TimerCallback, state, Timeout.Infinite, Timeout.Infinite);
var timer = new Timer(TimerCallback, new TimerState(key, act), Timeout.Infinite, Timeout.Infinite);
_timers.Add(key, timer); _timers.Add(key, timer);
timer.Change((long)due.TotalMilliseconds, (long)Timeout.Infinite); timer.Change(state.NextWait(), Timeout.InfiniteTimeSpan);
} }
} }
void TimerCallback(object? state) void TimerCallback(object? state)
{ {
var s = (TimerState)state!; var s = (TimerState)state!;
Task.Run(async () => Task.Run(async () =>
{ {
bool run = s.NextWait() == TimeSpan.Zero;
try try
{ {
await s.Act(); if (run)
await s.Act();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -62,7 +78,10 @@ namespace BTCPayServer.Services
{ {
if (_timers.TryGetValue(s.Key, out timer)) if (_timers.TryGetValue(s.Key, out timer))
{ {
_timers.Remove(s.Key); if (run)
_timers.Remove(s.Key);
else
timer.Change(s.NextWait(), Timeout.InfiniteTimeSpan);
} }
} }
timer?.Dispose(); timer?.Dispose();
@@ -80,5 +99,5 @@ namespace BTCPayServer.Services
_timers.Clear(); _timers.Clear();
} }
} }
} }
} }