diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index f6981d552..14e156769 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -8,7 +8,7 @@ namespace Discord /// /// Represents a generic Discord client. /// - public interface IDiscordClient : IDisposable + public interface IDiscordClient : IDisposable, IAsyncDisposable { /// /// Gets the current state of connection. diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 525875232..2bf08e3c7 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -149,9 +149,24 @@ namespace Discord.Rest _isDisposed = true; } } + + internal virtual async ValueTask DisposeAsync(bool disposing) + { + if (!_isDisposed) + { +#pragma warning disable IDISP007 + await ApiClient.DisposeAsync().ConfigureAwait(false); +#pragma warning restore IDISP007 + _stateLock?.Dispose(); + _isDisposed = true; + } + } + /// public void Dispose() => Dispose(true); + public ValueTask DisposeAsync() => DisposeAsync(true); + /// public Task GetRecommendedShardCountAsync(RequestOptions options = null) => ClientHelper.GetRecommendShardCountAsync(this, options); diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c2f2fbc99..c9a4d6c30 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -21,7 +21,7 @@ using System.Threading.Tasks; namespace Discord.API { - internal class DiscordRestApiClient : IDisposable + internal class DiscordRestApiClient : IDisposable, IAsyncDisposable { #region DiscordRestApiClient private static readonly ConcurrentDictionary> _bucketIdGenerators = new ConcurrentDictionary>(); @@ -97,8 +97,29 @@ namespace Discord.API _isDisposed = true; } } + + internal virtual async ValueTask DisposeAsync(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + _loginCancelToken?.Dispose(); + RestClient?.Dispose(); + + if (!(RequestQueue is null)) + await RequestQueue.DisposeAsync().ConfigureAwait(false); + + _stateLock?.Dispose(); + } + _isDisposed = true; + } + } + public void Dispose() => Dispose(true); + public ValueTask DisposeAsync() => DisposeAsync(true); + public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null) { await _stateLock.WaitAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 93183161b..cd2033c8c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -47,6 +47,14 @@ namespace Discord.Rest base.Dispose(disposing); } + internal override async ValueTask DisposeAsync(bool disposing) + { + if (disposing) + await ApiClient.DisposeAsync().ConfigureAwait(false); + + base.Dispose(disposing); + } + /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 2bf8e20b0..75e79eec2 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Bson; using System; using System.Collections.Concurrent; #if DEBUG_LIMITS @@ -10,7 +11,7 @@ using System.Threading.Tasks; namespace Discord.Net.Queue { - internal class RequestQueue : IDisposable + internal class RequestQueue : IDisposable, IAsyncDisposable { public event Func RateLimitTriggered; @@ -187,13 +188,31 @@ namespace Discord.Net.Queue await Task.Delay(60000, _cancelTokenSource.Token).ConfigureAwait(false); //Runs each minute } } - catch (OperationCanceledException) { } + catch (TaskCanceledException) { } catch (ObjectDisposedException) { } } public void Dispose() { - _cancelTokenSource?.Dispose(); + if (!(_cancelTokenSource is null)) + { + _cancelTokenSource.Cancel(); + _cancelTokenSource.Dispose(); + _cleanupTask.GetAwaiter().GetResult(); + } + _tokenLock?.Dispose(); + _clearToken?.Dispose(); + _requestCancelTokenSource?.Dispose(); + } + + public async ValueTask DisposeAsync() + { + if (!(_cancelTokenSource is null)) + { + _cancelTokenSource.Cancel(); + _cancelTokenSource.Dispose(); + await _cleanupTask.ConfigureAwait(false); + } _tokenLock?.Dispose(); _clearToken?.Dispose(); _requestCancelTokenSource?.Dispose(); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 9a13c8ff8..6e943dde3 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -568,6 +568,25 @@ namespace Discord.WebSocket base.Dispose(disposing); } + + internal override ValueTask DisposeAsync(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + if (_shards != null) + { + foreach (var client in _shards) + client?.Dispose(); + } + } + + _isDisposed = true; + } + + return base.DisposeAsync(disposing); + } #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 150da9d89..bf15967d3 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -124,12 +124,39 @@ namespace Discord.API _decompressor?.Dispose(); _compressed?.Dispose(); } - _isDisposed = true; } base.Dispose(disposing); } +#if NETSTANDARD2_1 + internal override async ValueTask DisposeAsync(bool disposing) +#else + internal override ValueTask DisposeAsync(bool disposing) +#endif + { + if (!_isDisposed) + { + if (disposing) + { + _connectCancelToken?.Dispose(); + (WebSocketClient as IDisposable)?.Dispose(); +#if NETSTANDARD2_1 + if (!(_decompressor is null)) + await _decompressor.DisposeAsync().ConfigureAwait(false); +#else + _decompressor?.Dispose(); +#endif + } + } + +#if NETSTANDARD2_1 + await base.DisposeAsync(disposing).ConfigureAwait(false); +#else + return base.DisposeAsync(disposing); +#endif + } + public async Task ConnectAsync() { await _stateLock.WaitAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index fa93239b5..69c16f88a 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -213,6 +213,27 @@ namespace Discord.WebSocket base.Dispose(disposing); } + + internal override async ValueTask DisposeAsync(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + await StopAsync().ConfigureAwait(false); + + if (!(ApiClient is null)) + await ApiClient.DisposeAsync().ConfigureAwait(false); + + _stateLock?.Dispose(); + } + _isDisposed = true; + } + + await base.DisposeAsync(disposing).ConfigureAwait(false); + } + + /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { if (_shardedClient == null && _defaultStickers.Length == 0 && AlwaysDownloadDefaultStickers)