Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch exceptions on a loop-level in Schedule service & Disable birthday scanner #76

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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