Skip to content

Commit

Permalink
Catch exceptions on a loop-level in Schedule service & Disable birthd…
Browse files Browse the repository at this point in the history
…ay scanner (#76)
  • Loading branch information
Twinki14 authored Oct 4, 2024
1 parent a96f06d commit 623ff89
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 51 deletions.
6 changes: 5 additions & 1 deletion src/Miha.Discord/Services/Hosted/GuildEventMonitorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public partial class GuildEventMonitorService(
.SetSize(1)
.SetAbsoluteExpiration(TimeSpan.FromMinutes(25))
.SetSlidingExpiration(TimeSpan.FromMinutes(15));

private readonly CronExpression _cron = CronExpression.Parse(Schedule, CronFormat.Standard);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
Expand Down Expand Up @@ -63,7 +64,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
catch (Exception e)
{
_logger.LogError(e, "Exception encountered in GuildEventMonitorService");
LogExceptionInBackgroundServiceLoop(e);

await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
Expand Down Expand Up @@ -197,4 +198,7 @@ private async Task CheckScheduledEventsAsync()

[LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "Exception occurred in GuildEventMonitorService during guildEvent {guildEventId}")]
public partial void LogError(Exception e, ulong guildEventId);

[LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Exception occurred in background service loop")]
public partial void LogExceptionInBackgroundServiceLoop(Exception e);
}
99 changes: 50 additions & 49 deletions src/Miha.Discord/Services/Hosted/GuildEventScheduleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Discord;
using Discord.Addons.Hosting;
using Discord.Addons.Hosting.Util;
using Discord.Net;
using Discord.WebSocket;
using Humanizer;
using Microsoft.Extensions.Logging;
Expand All @@ -27,49 +26,48 @@ public partial class GuildEventScheduleService(
private readonly DiscordSocketClient _client = client;
private readonly DiscordOptions _discordOptions = discordOptions.Value;
private readonly ILogger<GuildEventScheduleService> _logger = logger;

private const string Schedule = "0,5,10,15,20,25,30,35,40,45,50,55 * * * *"; // https://crontab.cronhub.io/

private readonly CronExpression _cron = CronExpression.Parse(Schedule, CronFormat.Standard);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Waiting for client to be ready...");

await Client.WaitForReadyAsync(stoppingToken);

while (!stoppingToken.IsCancellationRequested)
{
try
{
await PostWeeklyScheduleAsync();

var utcNow = easternStandardZonedClock.GetCurrentInstant().ToDateTimeUtc();
var nextUtc = _cron.GetNextOccurrence(DateTimeOffset.UtcNow, easternStandardZonedClock.GetTimeZoneInfo());

if (nextUtc is null)
{
_logger.LogWarning("Next utc occurence is null");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
continue;
}

var next = nextUtc.Value - utcNow;

_logger.LogDebug("Waiting {Time} until next operation", next.Humanize(3));

await Task.Delay(nextUtc.Value - utcNow, stoppingToken);

}
catch (HttpException e)
catch (Exception e)
{
_logger.LogWarning(e, "Discord dotnet http exception caught, likely caused by rate-limits, waiting a few minutes before continuing");

await Task.Delay(TimeSpan.FromMinutes(3), stoppingToken);

continue;
}

var utcNow = easternStandardZonedClock.GetCurrentInstant().ToDateTimeUtc();
var nextUtc = _cron.GetNextOccurrence(DateTimeOffset.UtcNow, easternStandardZonedClock.GetTimeZoneInfo());
LogExceptionInBackgroundServiceLoop(e);

if (nextUtc is null)
{
_logger.LogWarning("Next utc occurence is null");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
continue;
await Task.Delay(TimeSpan.FromMinutes(3), stoppingToken);
}

var next = nextUtc.Value - utcNow;

_logger.LogDebug("Waiting {Time} until next operation", next.Humanize(3));

await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
}

_logger.LogInformation("Hosted service ended");
}

Expand Down Expand Up @@ -98,41 +96,41 @@ private async Task PostWeeklyScheduleAsync()

var eventsThisWeekResult = await scheduledEventService.GetScheduledWeeklyEventsAsync(guild.Id, easternStandardZonedClock.GetCurrentDate());
var eventsThisWeek = eventsThisWeekResult.Value;

if (eventsThisWeekResult.IsFailed || eventsThisWeek is null)
{
_logger.LogWarning("Fetching this weeks events failed, or is null {Errors}", eventsThisWeekResult.Errors);
return;
}

var weeklyScheduleChannelResult = await guildService.GetWeeklyScheduleChannel(guild.Id);
var weeklyScheduleChannel = weeklyScheduleChannelResult.Value;

if (weeklyScheduleChannelResult.IsFailed || weeklyScheduleChannel is null)
{
_logger.LogWarning("Fetching the guilds weekly schedule channel failed, or is null {Errors}", weeklyScheduleChannelResult.Errors);
return;
}

var daysThisWeek = easternStandardZonedClock.GetCurrentWeekAsDates();

var eventsByDay = new Dictionary<DateOnly, IList<IGuildScheduledEvent>>();
var eventsThisWeekList = eventsThisWeek.ToList();
foreach (var dayOfWeek in daysThisWeek.OrderBy(d => d))
{
eventsByDay.Add(dayOfWeek, new List<IGuildScheduledEvent>());

foreach (var guildScheduledEvent in eventsThisWeekList.Where(e => easternStandardZonedClock.ToZonedDateTime(e.StartTime).Date.ToDateOnly() == dayOfWeek))
{
eventsByDay[dayOfWeek].Add(guildScheduledEvent);
}
}

_logger.LogInformation("Updating weekly schedule");

var postedHeader = false;
var postedFooter = false;

var messages = (await weeklyScheduleChannel
.GetMessagesAsync(50)
.FlattenAsync())
Expand All @@ -150,15 +148,15 @@ private async Task PostWeeklyScheduleAsync()
{
continue;
}

var messagesToDelete = messages
.Where(m => m.Author.Id == _client.CurrentUser.Id)
.ToList();

if (messagesToDelete.Any())
{
var deletedMessages = 0;

_logger.LogInformation("Wiping posted messages");

foreach (var message in messagesToDelete)
Expand All @@ -168,23 +166,23 @@ private async Task PostWeeklyScheduleAsync()
}

_logger.LogInformation("Deleted {DeletedMessages} messages", deletedMessages);

// Update the messages list
messages = (await weeklyScheduleChannel
.GetMessagesAsync(50)
.FlattenAsync())
.ToList();
}

break;
}

// TODO - Future me
// If the ordering becomes a problem, a potential solution could be to use an index
// to update the message at [1] (Tuesday), [6] (Sunday), [0] Monday for example
// this would ensure the order of messages align with the days of the week
// and to delete all messages from the bot if there's any more than 7 messages total

// Update (or post) a message with an embed of events for that day, for each day of the week
foreach (var (day, events) in eventsByDay)
{
Expand All @@ -204,15 +202,15 @@ private async Task PostWeeklyScheduleAsync()
.AtStartOfDayInZone(easternStandardZonedClock.GetTzdbTimeZone())
.ToInstant()
.ToDiscordTimestamp(TimestampTagStyles.ShortDate);

// The day has passed
if (easternStandardZonedClock.GetCurrentDate().ToDateOnly() > day)
{
description.AppendLine($"~~### {day.ToString("dddd")} - {timeStamp}~~");
} else
{
description.AppendLine($"### {day.ToString("dddd")} - {timeStamp}");

if (!events.Any())
{
description.AppendLine("*No events scheduled*");
Expand Down Expand Up @@ -248,7 +246,7 @@ private async Task PostWeeklyScheduleAsync()
}
}
}

if (!postedFooter && day == eventsByDay.Last().Key)
{
embed
Expand All @@ -257,7 +255,7 @@ private async Task PostWeeklyScheduleAsync()

postedFooter = true;
}

embed
.WithColor(new Color(255, 43, 241))
.WithDescription(description.ToString());
Expand All @@ -266,7 +264,7 @@ private async Task PostWeeklyScheduleAsync()
.FirstOrDefault(m =>
m.Author.Id == _client.CurrentUser.Id &&
m.Embeds.Any(e => e.Description.Contains(day.ToString("dddd"))));

if (lastPostedMessage is null)
{
_logger.LogInformation("Posting new message");
Expand All @@ -280,10 +278,13 @@ await weeklyScheduleChannel.ModifyMessageAsync(lastPostedMessage.Id, props =>
});
}
}

_logger.LogInformation("Finished updating weekly schedule");
}

[LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "Exception occurred")]
public partial void LogError(Exception e);

[LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "Exception occurred in background service loop")]
public partial void LogExceptionInBackgroundServiceLoop(Exception e);
}
3 changes: 2 additions & 1 deletion src/Miha/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ private static IServiceCollection AddHealthServices(this IServiceCollection serv

private static IServiceCollection AddBackgroundServices(this IServiceCollection services)
{
services.AddHostedService<BirthdayScannerService>();
// TODO - Disabled until Birthdays are actually implemented
//services.AddHostedService<BirthdayScannerService>();

return services;
}
Expand Down

0 comments on commit 623ff89

Please sign in to comment.