mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-24 01:14:12 +00:00
Reduce concurrent database operations in background services
This commit is contained in:
parent
1bc241577a
commit
31d5513c9e
4 changed files with 32 additions and 11 deletions
|
@ -1,6 +1,7 @@
|
|||
namespace BirthdayBot.BackgroundServices;
|
||||
|
||||
abstract class BackgroundService {
|
||||
protected static SemaphoreSlim DbConcurrentOperationsLock { get; } = new(ShardManager.MaxConcurrentOperations);
|
||||
protected ShardInstance ShardInstance { get; }
|
||||
|
||||
public BackgroundService(ShardInstance instance) => ShardInstance = instance;
|
||||
|
|
|
@ -3,7 +3,6 @@ using NodaTime;
|
|||
using System.Text;
|
||||
|
||||
namespace BirthdayBot.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// Core automatic functionality of the bot. Manages role memberships based on birthday information,
|
||||
/// and optionally sends the announcement message to appropriate guilds.
|
||||
|
@ -15,11 +14,22 @@ class BirthdayRoleUpdate : BackgroundService {
|
|||
/// Processes birthday updates for all available guilds synchronously.
|
||||
/// </summary>
|
||||
public override async Task OnTick(int tickCount, CancellationToken token) {
|
||||
try {
|
||||
await DbConcurrentOperationsLock.WaitAsync(token);
|
||||
await ProcessBirthdaysAsync(token);
|
||||
} finally {
|
||||
try {
|
||||
DbConcurrentOperationsLock.Release();
|
||||
} catch (ObjectDisposedException) { }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessBirthdaysAsync(CancellationToken token) {
|
||||
// For database efficiency, fetch all database information at once before proceeding
|
||||
using var db = new BotDatabaseContext();
|
||||
var shardGuilds = ShardInstance.DiscordClient.Guilds.Select(g => (long)g.Id).ToHashSet();
|
||||
var presentGuildSettings = db.GuildConfigurations.Where(s => shardGuilds.Contains(s.GuildId));
|
||||
var guildChecks = presentGuildSettings.ToList().Select(s => new Tuple<ulong, GuildConfig>((ulong)s.GuildId, s));
|
||||
var guildChecks = presentGuildSettings.ToList().Select(s => Tuple.Create((ulong)s.GuildId, s));
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var (guildId, settings) in guildChecks) {
|
||||
|
@ -44,7 +54,7 @@ class BirthdayRoleUpdate : BackgroundService {
|
|||
// Invalid role was configured. Clear the setting and quit.
|
||||
settings.RoleId = null;
|
||||
db.Update(settings);
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -102,13 +112,13 @@ class BirthdayRoleUpdate : BackgroundService {
|
|||
/// Gets all known users from the given guild and returns a list including only those who are
|
||||
/// currently experiencing a birthday in the respective time zone.
|
||||
/// </summary>
|
||||
public static HashSet<ulong> GetGuildCurrentBirthdays(IEnumerable<UserEntry> guildUsers, string? ServerDefaultTzId) {
|
||||
public static HashSet<ulong> GetGuildCurrentBirthdays(IEnumerable<UserEntry> guildUsers, string? serverDefaultTzId) {
|
||||
var birthdayUsers = new HashSet<ulong>();
|
||||
|
||||
foreach (var record in guildUsers) {
|
||||
// Determine final time zone to use for calculation
|
||||
DateTimeZone tz = DateTimeZoneProviders.Tzdb
|
||||
.GetZoneOrNull(record.TimeZone ?? ServerDefaultTzId ?? "UTC")!;
|
||||
.GetZoneOrNull(record.TimeZone ?? serverDefaultTzId ?? "UTC")!;
|
||||
|
||||
var checkNow = SystemClock.Instance.GetCurrentInstant().InZone(tz);
|
||||
// Special case: If user's birthday is 29-Feb and it's currently not a leap year, check against 1-Mar
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
using System.Text;
|
||||
|
||||
namespace BirthdayBot.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically removes database information for guilds that have not been accessed in a long time.
|
||||
/// </summary>
|
||||
class DataRetention : BackgroundService {
|
||||
private static readonly SemaphoreSlim _updateLock = new(ShardManager.MaxConcurrentOperations);
|
||||
const int ProcessInterval = 5400 / ShardBackgroundWorker.Interval; // Process about once per hour and a half
|
||||
|
||||
// Amount of days without updates before data is considered stale and up for deletion.
|
||||
const int StaleGuildThreshold = 180;
|
||||
const int StaleUserThreashold = 360;
|
||||
|
@ -19,6 +18,17 @@ class DataRetention : BackgroundService {
|
|||
// On each tick, run only a set group of guilds, each group still processed every ProcessInterval ticks.
|
||||
if ((tickCount + ShardInstance.ShardId) % ProcessInterval != 0) return;
|
||||
|
||||
try {
|
||||
await DbConcurrentOperationsLock.WaitAsync(token);
|
||||
await RemoveStaleEntriesAsync();
|
||||
} finally {
|
||||
try {
|
||||
DbConcurrentOperationsLock.Release();
|
||||
} catch (ObjectDisposedException) { }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveStaleEntriesAsync() {
|
||||
using var db = new BotDatabaseContext();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
int updatedGuilds = 0, updatedUsers = 0;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
namespace BirthdayBot.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the execution of periodic background tasks specific to each shard.
|
||||
/// </summary>
|
||||
|
@ -54,7 +53,7 @@ class ShardBackgroundWorker : IDisposable {
|
|||
await Task.Delay(Interval * 1000, _workerCanceller.Token).ConfigureAwait(false);
|
||||
|
||||
// Skip this round of task execution if the client is not connected
|
||||
if (Instance.DiscordClient.ConnectionState != Discord.ConnectionState.Connected) continue;
|
||||
if (Instance.DiscordClient.ConnectionState != ConnectionState.Connected) continue;
|
||||
|
||||
// Execute tasks sequentially
|
||||
foreach (var service in _workers) {
|
||||
|
@ -62,8 +61,9 @@ class ShardBackgroundWorker : IDisposable {
|
|||
try {
|
||||
if (_workerCanceller.IsCancellationRequested) break;
|
||||
_tickCount++;
|
||||
await service.OnTick(_tickCount, _workerCanceller.Token).ConfigureAwait(false);
|
||||
} catch (Exception ex) when (ex is not TaskCanceledException) {
|
||||
await service.OnTick(_tickCount, _workerCanceller.Token);
|
||||
} catch (Exception ex) when (ex is not
|
||||
(TaskCanceledException or OperationCanceledException or ObjectDisposedException)) {
|
||||
Instance.Log(CurrentExecutingService, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue