Skip to content

Commit

Permalink
Merge pull request #163 from JohannesKauffmann/semaphore-finally
Browse files Browse the repository at this point in the history
Timers: acquire/release semaphores in try/finally pair
  • Loading branch information
Naamloos committed May 24, 2023
2 parents ca1d29c + 2636d00 commit 6f4de2c
Showing 1 changed file with 42 additions and 36 deletions.
78 changes: 42 additions & 36 deletions ModCore/Listeners/Timers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public static DatabaseTimer FindTimer(int id, TimerActionType actionType, ulong

public static async Task UnscheduleTimersAsync(params DatabaseTimer[] timers)
{
// lock the timers
await semaphore.WaitAsync();

try
{
// lock the timers
await semaphore.WaitAsync();

// remove the requested timers
using (var db = databaseContextBuilder.CreateContext())
{
Expand All @@ -82,47 +82,53 @@ public static async Task UnscheduleTimersAsync(params DatabaseTimer[] timers)

public static async Task ScheduleNextAsync()
{
await semaphore.WaitAsync();
await TriggerExpiredTimersAsync();

using var dbContext = databaseContextBuilder.CreateContext();
if (!dbContext.Timers.Any())
try
{
return; // We'll reschedule once a new timer arrives through a command.
}
await semaphore.WaitAsync();
await TriggerExpiredTimersAsync();

var newTimer = dbContext.Timers.OrderBy(x => x.DispatchAt).FirstOrDefault();
if (newTimer != null)
{
// Cancel previous if necessary
if (current.timer == null || current.timer.DispatchAt > newTimer.DispatchAt)
using var dbContext = databaseContextBuilder.CreateContext();
if (!dbContext.Timers.Any())
{
if (current.cancellation != null)
{
// We cancel our previous dispatch / interim reschedule
current.cancellation.Cancel();
}

// Schedule next
current.cancellation = new CancellationTokenSource();
return; // We'll reschedule once a new timer arrives through a command.
}

var delay = newTimer.DispatchAt.Subtract(DateTime.Now);
// There's a max to a delay. If it's over the max, we reschedule on trigger. Else, just schedule dispatch.
if (delay.TotalMilliseconds > Int32.MaxValue)
{
current.timer = null;
_ = Task.Delay(TimeSpan.FromMilliseconds(Int32.MaxValue - 1000/* just to be safe */), current.cancellation.Token)
.ContinueWith(InterimScheduleNextAsync, current.cancellation, TaskContinuationOptions.OnlyOnRanToCompletion);
}
else
var newTimer = dbContext.Timers.OrderBy(x => x.DispatchAt).FirstOrDefault();
if (newTimer != null)
{
// Cancel previous if necessary
if (current.timer == null || current.timer.DispatchAt > newTimer.DispatchAt)
{
current.timer = newTimer;
_ = Task.Delay(delay, current.cancellation.Token)
.ContinueWith(DispatchAsync, current.timer, TaskContinuationOptions.OnlyOnRanToCompletion);
if (current.cancellation != null)
{
// We cancel our previous dispatch / interim reschedule
current.cancellation.Cancel();
}

// Schedule next
current.cancellation = new CancellationTokenSource();

var delay = newTimer.DispatchAt.Subtract(DateTime.Now);
// There's a max to a delay. If it's over the max, we reschedule on trigger. Else, just schedule dispatch.
if (delay.TotalMilliseconds > Int32.MaxValue)
{
current.timer = null;
_ = Task.Delay(TimeSpan.FromMilliseconds(Int32.MaxValue - 1000/* just to be safe */), current.cancellation.Token)
.ContinueWith(InterimScheduleNextAsync, current.cancellation, TaskContinuationOptions.OnlyOnRanToCompletion);
}
else
{
current.timer = newTimer;
_ = Task.Delay(delay, current.cancellation.Token)
.ContinueWith(DispatchAsync, current.timer, TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
}
}
semaphore.Release();
finally
{
semaphore.Release();
}
}

private static async Task TriggerExpiredTimersAsync()
Expand Down

0 comments on commit 6f4de2c

Please sign in to comment.