From 41653a317bfe52948ae4d555ba8c452d0a5428cd Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 2 Feb 2022 06:28:33 +0100 Subject: [PATCH] More PeriodicTimer's instead of System.Threading.Timers --- .../PlayingRotate/PlayingRotateService.cs | 2 +- .../ServerLog/ServerLogCommandService.cs | 28 +++--- .../UserPunish/UserPunishService.cs | 2 +- .../Modules/Gambling/GamblingService.cs | 56 +++++------ src/NadekoBot/Modules/Games/GamesService.cs | 23 ++--- .../StreamNotificationService.cs | 95 ++++++++++--------- .../Utility/Patreon/PatreonRewardsService.cs | 2 +- .../UnitConversion/ConverterService.cs | 2 +- src/NadekoBot/Services/CommandHandler.cs | 22 +++-- src/NadekoBot/Services/Impl/StatsService.cs | 2 +- 10 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs b/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs index db40f679e..aad897d3f 100644 --- a/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs +++ b/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs @@ -29,7 +29,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor public async Task OnReadyAsync() { - var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); var index = 0; while (await timer.WaitForNextTickAsync()) { diff --git a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs index 4e076e57c..c8920e73c 100644 --- a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs +++ b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs @@ -21,8 +21,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor private readonly GuildTimezoneService _tz; private readonly IEmbedBuilderService _eb; private readonly IMemoryCache _memoryCache; - - private readonly Timer _clearTimer; + private readonly ConcurrentHashSet _ignoreMessageIds = new(); public LogCommandService( @@ -80,21 +79,25 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor _prot.OnAntiProtectionTriggered += TriggeredAntiProtection; - _clearTimer = new(_ => - { - _ignoreMessageIds.Clear(); - }, - null, - TimeSpan.FromHours(1), - TimeSpan.FromHours(1)); - #endif } - public async Task OnReadyAsync() + public Task OnReadyAsync() + => Task.WhenAll(PresenceUpdateTask(), IgnoreMessageIdsClearTask()); + + private async Task IgnoreMessageIdsClearTask() + { + using var timer = new PeriodicTimer(TimeSpan.FromHours(1)); + while (await timer.WaitForNextTickAsync()) + { + _ignoreMessageIds.Clear(); + } + } + + private async Task PresenceUpdateTask() { #if !GLOBAL_NADEKO - var timer = new PeriodicTimer(TimeSpan.FromSeconds(15)); + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(15)); while (await timer.WaitForNextTickAsync()) { try @@ -105,6 +108,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor { if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) return Task.CompletedTask; + if (PresenceUpdates.TryRemove(key, out var msgs)) { var title = GetText(key.Guild, strs.presence_updates); diff --git a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs index b8c308c28..602f4fe23 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -36,7 +36,7 @@ public class UserPunishService : INService, IReadyExecutor if (_client.ShardId != 0) return; - var expiryTimer = new PeriodicTimer(TimeSpan.FromHours(12)); + using var expiryTimer = new PeriodicTimer(TimeSpan.FromHours(12)); do { try diff --git a/src/NadekoBot/Modules/Gambling/GamblingService.cs b/src/NadekoBot/Modules/Gambling/GamblingService.cs index 85a82d306..19e309768 100644 --- a/src/NadekoBot/Modules/Gambling/GamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/GamblingService.cs @@ -1,5 +1,6 @@ #nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common.Connect4; @@ -9,7 +10,7 @@ using Newtonsoft.Json; namespace NadekoBot.Modules.Gambling.Services; -public class GamblingService : INService +public class GamblingService : INService, IReadyExecutor { public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new(); public ConcurrentDictionary Connect4Games { get; } = new(); @@ -20,8 +21,6 @@ public class GamblingService : INService private readonly IDataCache _cache; private readonly GamblingConfigService _gss; - private readonly Timer _decayTimer; - public GamblingService( DbService db, Bot bot, @@ -36,32 +35,38 @@ public class GamblingService : INService _client = client; _cache = cache; _gss = gss; + } - if (_bot.Client.ShardId == 0) - _decayTimer = new(_ => - { - var config = _gss.Data; - var maxDecay = config.Decay.MaxDecay; - if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) - return; + public async Task OnReadyAsync() + { + if (_bot.Client.ShardId != 0) + return; + + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5)); + while (await timer.WaitForNextTickAsync()) + { + var config = _gss.Data; + var maxDecay = config.Decay.MaxDecay; + if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) + continue; - using var uow = _db.GetDbContext(); - var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); + await using var uow = _db.GetDbContext(); + var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); - if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) - return; + if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) + continue; - Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}% + Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}% | max: {MaxDecay} | threshold: {DecayMinTreshold}", - config.Decay.Percent * 100, - maxDecay, - config.Decay.MinThreshold); + config.Decay.Percent * 100, + maxDecay, + config.Decay.MinThreshold); - if (maxDecay == 0) - maxDecay = int.MaxValue; + if (maxDecay == 0) + maxDecay = int.MaxValue; - uow.Database.ExecuteSqlInterpolated($@" + await uow.Database.ExecuteSqlInterpolatedAsync($@" UPDATE DiscordUser SET CurrencyAmount= CASE WHEN @@ -73,12 +78,9 @@ SET CurrencyAmount= END WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};"); - _cache.SetLastCurrencyDecay(); - uow.SaveChanges(); - }, - null, - TimeSpan.FromMinutes(5), - TimeSpan.FromMinutes(5)); + _cache.SetLastCurrencyDecay(); + await uow.SaveChangesAsync(); + } } public async Task SlotAsync(ulong userId, long amount) diff --git a/src/NadekoBot/Modules/Games/GamesService.cs b/src/NadekoBot/Modules/Games/GamesService.cs index 5b1fbd4c7..457eebcdb 100644 --- a/src/NadekoBot/Modules/Games/GamesService.cs +++ b/src/NadekoBot/Modules/Games/GamesService.cs @@ -1,5 +1,6 @@ #nullable disable using Microsoft.Extensions.Caching.Memory; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Modules.Games.Common; using NadekoBot.Modules.Games.Common.Acrophobia; using NadekoBot.Modules.Games.Common.Nunchi; @@ -8,7 +9,7 @@ using Newtonsoft.Json; namespace NadekoBot.Modules.Games.Services; -public class GamesService : INService +public class GamesService : INService, IReadyExecutor { private const string TYPING_ARTICLES_PATH = "data/typing_articles3.json"; @@ -29,7 +30,6 @@ public class GamesService : INService public AsyncLazy Ratings { get; } private readonly GamesConfigService _gamesConfig; - private readonly Timer _t; private readonly IHttpClientFactory _httpFactory; private readonly IMemoryCache _8BallCache; private readonly Random _rng; @@ -46,15 +46,6 @@ public class GamesService : INService Ratings = new(GetRatingTexts); _rng = new NadekoRandom(); - //girl ratings - _t = new(_ => - { - GirlRatings.Clear(); - }, - null, - TimeSpan.FromDays(1), - TimeSpan.FromDays(1)); - try { TypingArticles = JsonConvert.DeserializeObject>(File.ReadAllText(TYPING_ARTICLES_PATH)); @@ -66,6 +57,16 @@ public class GamesService : INService } } + public async Task OnReadyAsync() + { + // reset rating once a day + using var timer = new PeriodicTimer(TimeSpan.FromDays(1)); + while (await timer.WaitForNextTickAsync()) + { + GirlRatings.Clear(); + } + } + private async Task GetRatingTexts() { using var http = _httpFactory.CreateClient(); diff --git a/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs b/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs index ca708ecf0..46d4fdeba 100644 --- a/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs +++ b/src/NadekoBot/Modules/Searches/StreamNotification/StreamNotificationService.cs @@ -1,5 +1,6 @@ #nullable disable using Microsoft.EntityFrameworkCore; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using NadekoBot.Db.Models; using NadekoBot.Modules.Searches.Common; @@ -9,7 +10,7 @@ using StackExchange.Redis; namespace NadekoBot.Modules.Searches.Services; -public sealed class StreamNotificationService : INService +public sealed class StreamNotificationService : INService, IReadyExecutor { private readonly DbService _db; private readonly IBotStrings _strings; @@ -26,7 +27,6 @@ public sealed class StreamNotificationService : INService private readonly IPubSub _pubSub; private readonly IEmbedBuilderService _eb; - private readonly Timer _notifCleanupTimer; private readonly TypedKey> _streamsOnlineKey; private readonly TypedKey> _streamsOfflineKey; @@ -114,49 +114,6 @@ public sealed class StreamNotificationService : INService _streamTracker.OnStreamsOffline += OnStreamsOffline; _streamTracker.OnStreamsOnline += OnStreamsOnline; _ = _streamTracker.RunAsync(); - _notifCleanupTimer = new(_ => - { - try - { - var errorLimit = TimeSpan.FromHours(12); - var failingStreams = _streamTracker.GetFailingStreams(errorLimit, true).ToList(); - - if (!failingStreams.Any()) - return; - - var deleteGroups = failingStreams.GroupBy(x => x.Type) - .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList()); - - using var uow = _db.GetDbContext(); - foreach (var kvp in deleteGroups) - { - Log.Information( - "Deleting {StreamCount} {Platform} streams because they've been erroring for more than {ErrorLimit}: {RemovedList}", - kvp.Value.Count, - kvp.Key, - errorLimit, - string.Join(", ", kvp.Value)); - - var toDelete = uow.Set() - .AsQueryable() - .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) - .ToList(); - - uow.RemoveRange(toDelete); - uow.SaveChanges(); - - foreach (var loginToDelete in kvp.Value) - _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete)); - } - } - catch (Exception ex) - { - Log.Error(ex, "Error cleaning up FollowedStreams"); - } - }, - null, - TimeSpan.FromMinutes(30), - TimeSpan.FromMinutes(30)); _pubSub.Sub(_streamFollowKey, HandleFollowStream); _pubSub.Sub(_streamUnfollowKey, HandleUnfollowStream); @@ -166,6 +123,54 @@ public sealed class StreamNotificationService : INService client.LeftGuild += ClientOnLeftGuild; } + public async Task OnReadyAsync() + { + if (_client.ShardId != 0) + return; + + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(30)); + while (await timer.WaitForNextTickAsync()) + { + try + { + var errorLimit = TimeSpan.FromHours(12); + var failingStreams = _streamTracker.GetFailingStreams(errorLimit, true).ToList(); + + if (!failingStreams.Any()) + continue; + + var deleteGroups = failingStreams.GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList()); + + await using var uow = _db.GetDbContext(); + foreach (var kvp in deleteGroups) + { + Log.Information( + "Deleting {StreamCount} {Platform} streams because they've been erroring for more than {ErrorLimit}: {RemovedList}", + kvp.Value.Count, + kvp.Key, + errorLimit, + string.Join(", ", kvp.Value)); + + var toDelete = uow.Set() + .AsQueryable() + .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) + .ToList(); + + uow.RemoveRange(toDelete); + await uow.SaveChangesAsync(); + + foreach (var loginToDelete in kvp.Value) + _streamTracker.UntrackStreamByKey(new(kvp.Key, loginToDelete)); + } + } + catch (Exception ex) + { + Log.Error(ex, "Error cleaning up FollowedStreams"); + } + } + } + /// /// Handles follow stream pubs to keep the counter up to date. /// When counter reaches 0, stream is removed from tracking because diff --git a/src/NadekoBot/Modules/Utility/Patreon/PatreonRewardsService.cs b/src/NadekoBot/Modules/Utility/Patreon/PatreonRewardsService.cs index 28e5455e9..83944c32b 100644 --- a/src/NadekoBot/Modules/Utility/Patreon/PatreonRewardsService.cs +++ b/src/NadekoBot/Modules/Utility/Patreon/PatreonRewardsService.cs @@ -52,7 +52,7 @@ public class PatreonRewardsService : INService, IReadyExecutor if (_client.ShardId != 0) return; - var t = new PeriodicTimer(Interval); + using var t = new PeriodicTimer(Interval); do { try diff --git a/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs b/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs index 3a55d3509..e1acdb97e 100644 --- a/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs +++ b/src/NadekoBot/Modules/Utility/UnitConversion/ConverterService.cs @@ -30,7 +30,7 @@ public class ConverterService : INService, IReadyExecutor if (_client.ShardId != 0) return; - var timer = new PeriodicTimer(_updateInterval); + using var timer = new PeriodicTimer(_updateInterval); do { try diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index c136084af..f8823be47 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -1,5 +1,6 @@ #nullable disable using NadekoBot.Common.Configs; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using System.Collections.Immutable; using ExecuteResult = Discord.Commands.ExecuteResult; @@ -7,7 +8,7 @@ using PreconditionResult = Discord.Commands.PreconditionResult; namespace NadekoBot.Services; -public class CommandHandler : INService +public class CommandHandler : INService, IReadyExecutor { private const int GLOBAL_COMMANDS_COOLDOWN = 750; @@ -30,7 +31,6 @@ public class CommandHandler : INService private readonly IServiceProvider _services; private readonly ConcurrentDictionary _prefixes; - private readonly Timer _clearUsersOnShortCooldown; private readonly DbService _db; // private readonly InteractionService _interactions; @@ -53,19 +53,21 @@ public class CommandHandler : INService _services = services; // _interactions = interactions; - _clearUsersOnShortCooldown = new(_ => - { - UsersOnShortCooldown.Clear(); - }, - null, - GLOBAL_COMMANDS_COOLDOWN, - GLOBAL_COMMANDS_COOLDOWN); - _prefixes = bot.AllGuildConfigs.Where(x => x.Prefix is not null) .ToDictionary(x => x.GuildId, x => x.Prefix) .ToConcurrent(); } + public async Task OnReadyAsync() + { + // clear users on short cooldown every GLOBAL_COMMANDS_COOLDOWN miliseconds + using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(GLOBAL_COMMANDS_COOLDOWN)); + while (await timer.WaitForNextTickAsync()) + { + UsersOnShortCooldown.Clear(); + } + } + public string GetPrefix(IGuild guild) => GetPrefix(guild?.Id); diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index d89acc9d5..98dfd0306 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -139,7 +139,7 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel)); voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel)); - var timer = new PeriodicTimer(TimeSpan.FromHours(1)); + using var timer = new PeriodicTimer(TimeSpan.FromHours(1)); do { if (string.IsNullOrWhiteSpace(_creds.BotListToken))