diff --git a/ArchiSteamFarm/Core/RemoteCommunication.cs b/ArchiSteamFarm/Core/RemoteCommunication.cs index 163dc0cc7..0dde46a1a 100644 --- a/ArchiSteamFarm/Core/RemoteCommunication.cs +++ b/ArchiSteamFarm/Core/RemoteCommunication.cs @@ -41,7 +41,7 @@ using ArchiSteamFarm.Web; namespace ArchiSteamFarm.Core; -internal sealed class RemoteCommunication : IAsyncDisposable { +internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch @@ -59,11 +59,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable { private readonly Bot Bot; private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1); - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private readonly Timer? MatchActivelyTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private readonly SemaphoreSlim RequestsSemaphore = new(1, 1); private DateTime LastAnnouncementCheck; @@ -84,10 +80,21 @@ internal sealed class RemoteCommunication : IAsyncDisposable { } } - public async ValueTask DisposeAsync() { + public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception MatchActivelySemaphore.Dispose(); RequestsSemaphore.Dispose(); + // Those are objects that might be null and the check should be in-place + MatchActivelyTimer?.Dispose(); + } + + public async ValueTask DisposeAsync() { + // Those are objects that are always being created if constructor doesn't throw exception + MatchActivelySemaphore.Dispose(); + RequestsSemaphore.Dispose(); + + // Those are objects that might be null and the check should be in-place if (MatchActivelyTimer != null) { await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false); } diff --git a/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs b/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs index eaaa6a85d..1adea5072 100644 --- a/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs +++ b/ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs @@ -29,7 +29,7 @@ using ArchiSteamFarm.Core; namespace ArchiSteamFarm.Helpers; -internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, IDisposable { +internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossProcessSemaphore, IDisposable { private const ushort SpinLockDelay = 1000; // In milliseconds private readonly string FilePath; @@ -48,11 +48,23 @@ internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, I } public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception LocalSemaphore.Dispose(); + // Those are objects that might be null and the check should be in-place FileLock?.Dispose(); } + public async ValueTask DisposeAsync() { + // Those are objects that are always being created if constructor doesn't throw exception + LocalSemaphore.Dispose(); + + // Those are objects that might be null and the check should be in-place + if (FileLock != null) { + await FileLock.DisposeAsync().ConfigureAwait(false); + } + } + void ICrossProcessSemaphore.Release() { // ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that lock (LocalSemaphore) { diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 1ef622bf1..977cc0ad2 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -58,7 +58,7 @@ using SteamKit2.Internal; namespace ArchiSteamFarm.Steam; -public sealed class Bot : IAsyncDisposable { +public sealed class Bot : IAsyncDisposable, IDisposable { internal const ushort CallbackSleep = 500; // In milliseconds internal const byte MinCardsPerBadge = 5; @@ -144,11 +144,7 @@ public sealed class Bot : IAsyncDisposable { private readonly CallbackManager CallbackManager; private readonly SemaphoreSlim CallbackSemaphore = new(1, 1); private readonly SemaphoreSlim GamesRedeemerInBackgroundSemaphore = new(1, 1); - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private readonly Timer HeartBeatTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private readonly SemaphoreSlim InitializationSemaphore = new(1, 1); private readonly SemaphoreSlim MessagingSemaphore = new(1, 1); private readonly ConcurrentDictionary PastNotifications = new(); @@ -226,35 +222,20 @@ public sealed class Bot : IAsyncDisposable { [JsonProperty] private string? AvatarHash; -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private Timer? ConnectionFailureTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private bool FirstTradeSent; - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private Timer? GamesRedeemerInBackgroundTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private byte HeartBeatFailures; private EResult LastLogOnResult; private DateTime LastLogonSessionReplaced; private bool LibraryLocked; private byte LoginFailures; private ulong MasterChatGroupID; - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private Timer? PlayingWasBlockedTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private bool ReconnectOnUserInitiated; private RemoteCommunication? RemoteCommunication; private bool SendCompleteTypesScheduled; - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private Timer? SendItemsTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private bool SteamParentalActive; private SteamSaleEvent? SteamSaleEvent; private string? TwoFactorCode; @@ -345,15 +326,39 @@ public sealed class Bot : IAsyncDisposable { ); } + public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception + ArchiWebHandler.Dispose(); + BotDatabase.Dispose(); + CallbackSemaphore.Dispose(); + GamesRedeemerInBackgroundSemaphore.Dispose(); + InitializationSemaphore.Dispose(); + MessagingSemaphore.Dispose(); + SendCompleteTypesSemaphore.Dispose(); + Trading.Dispose(); + + Actions.Dispose(); + CardsFarmer.Dispose(); + HeartBeatTimer.Dispose(); + + // Those are objects that might be null and the check should be in-place + ConnectionFailureTimer?.Dispose(); + GamesRedeemerInBackgroundTimer?.Dispose(); + PlayingWasBlockedTimer?.Dispose(); + RemoteCommunication?.Dispose(); + SendItemsTimer?.Dispose(); + SteamSaleEvent?.Dispose(); + } + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception ArchiWebHandler.Dispose(); BotDatabase.Dispose(); CallbackSemaphore.Dispose(); GamesRedeemerInBackgroundSemaphore.Dispose(); - SendCompleteTypesSemaphore.Dispose(); InitializationSemaphore.Dispose(); MessagingSemaphore.Dispose(); + SendCompleteTypesSemaphore.Dispose(); Trading.Dispose(); await Actions.DisposeAsync().ConfigureAwait(false); diff --git a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs index d85c413c1..caeeb19dc 100644 --- a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs +++ b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs @@ -42,7 +42,7 @@ using SteamKit2; namespace ArchiSteamFarm.Steam.Cards; -public sealed class CardsFarmer : IAsyncDisposable { +public sealed class CardsFarmer : IAsyncDisposable, IDisposable { internal const byte DaysForRefund = 14; // In how many days since payment we're allowed to refund internal const byte HoursForRefund = 2; // Up to how many hours we're allowed to play for refund @@ -120,10 +120,7 @@ public sealed class CardsFarmer : IAsyncDisposable { private readonly SemaphoreSlim FarmingInitializationSemaphore = new(1, 1); private readonly SemaphoreSlim FarmingResetSemaphore = new(0, 1); private readonly ConcurrentList GamesToFarm = new(); - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private readonly Timer? IdleFarmingTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() private readonly ConcurrentDictionary LocallyIgnoredAppIDs = new(); @@ -160,6 +157,16 @@ public sealed class CardsFarmer : IAsyncDisposable { } } + public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception + EventSemaphore.Dispose(); + FarmingInitializationSemaphore.Dispose(); + FarmingResetSemaphore.Dispose(); + + // Those are objects that might be null and the check should be in-place + IdleFarmingTimer?.Dispose(); + } + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception EventSemaphore.Dispose(); diff --git a/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs b/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs index c72a3a83b..1e70a54a6 100644 --- a/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs +++ b/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs @@ -30,14 +30,11 @@ using ArchiSteamFarm.Localization; namespace ArchiSteamFarm.Steam.Integration; -internal sealed class SteamSaleEvent : IAsyncDisposable { +internal sealed class SteamSaleEvent : IAsyncDisposable, IDisposable { private const byte MaxSingleQueuesDaily = 3; // This is only a failsafe for infinite queue clearing (in case IsDiscoveryQueueAvailable() would fail us) private readonly Bot Bot; - -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private readonly Timer SaleEventTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() internal SteamSaleEvent(Bot bot) { Bot = bot ?? throw new ArgumentNullException(nameof(bot)); @@ -50,6 +47,8 @@ internal sealed class SteamSaleEvent : IAsyncDisposable { ); } + public void Dispose() => SaleEventTimer.Dispose(); + public ValueTask DisposeAsync() => SaleEventTimer.DisposeAsync(); private async void ExploreDiscoveryQueue(object? state = null) { diff --git a/ArchiSteamFarm/Steam/Interaction/Actions.cs b/ArchiSteamFarm/Steam/Interaction/Actions.cs index d339739fd..922bfadf7 100644 --- a/ArchiSteamFarm/Steam/Interaction/Actions.cs +++ b/ArchiSteamFarm/Steam/Interaction/Actions.cs @@ -42,22 +42,27 @@ using SteamKit2; namespace ArchiSteamFarm.Steam.Interaction; -public sealed class Actions : IAsyncDisposable { +public sealed class Actions : IAsyncDisposable, IDisposable { private static readonly SemaphoreSlim GiftCardsSemaphore = new(1, 1); private readonly Bot Bot; private readonly ConcurrentHashSet HandledGifts = new(); private readonly SemaphoreSlim TradingSemaphore = new(1, 1); -#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync() private Timer? CardsFarmerResumeTimer; -#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync() - private bool ProcessingGiftsScheduled; private bool TradingScheduled; internal Actions(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot)); + public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception + TradingSemaphore.Dispose(); + + // Those are objects that might be null and the check should be in-place + CardsFarmerResumeTimer?.Dispose(); + } + public async ValueTask DisposeAsync() { // Those are objects that are always being created if constructor doesn't throw exception TradingSemaphore.Dispose(); diff --git a/ArchiSteamFarm/Web/Responses/StreamResponse.cs b/ArchiSteamFarm/Web/Responses/StreamResponse.cs index f85151488..c7c62488a 100644 --- a/ArchiSteamFarm/Web/Responses/StreamResponse.cs +++ b/ArchiSteamFarm/Web/Responses/StreamResponse.cs @@ -27,7 +27,7 @@ using JetBrains.Annotations; namespace ArchiSteamFarm.Web.Responses; -public sealed class StreamResponse : BasicResponse, IAsyncDisposable { +public sealed class StreamResponse : BasicResponse, IAsyncDisposable, IDisposable { [PublicAPI] public Stream? Content { get; } @@ -43,6 +43,11 @@ public sealed class StreamResponse : BasicResponse, IAsyncDisposable { Length = httpResponseMessage.Content.Headers.ContentLength.GetValueOrDefault(); } + public void Dispose() { + Content?.Dispose(); + ResponseMessage.Dispose(); + } + public async ValueTask DisposeAsync() { if (Content != null) { await Content.DisposeAsync().ConfigureAwait(false);