Skip to content

Commit cd36bb8

Browse files
Jagreaperquinchs
andauthored
Fix OperationCancelledException and add IAsyncEnumerable to wait without thread blocking (#1562)
* Fixed Task Scheduler operation cancelled error caused by Orphaned RunCleanup task on RequestQueue not being awaited on dispose * Added async disposable interface to various components * Added incorrect early marking of disposed for DiscordSocketApi client * Added general task canceled exception catch to cleanup task * Fix merge errors Co-authored-by: Quin Lynch <[email protected]> Co-authored-by: quin lynch <[email protected]>
1 parent 4ed4718 commit cd36bb8

File tree

8 files changed

+136
-6
lines changed

8 files changed

+136
-6
lines changed

src/Discord.Net.Core/IDiscordClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Discord
88
/// <summary>
99
/// Represents a generic Discord client.
1010
/// </summary>
11-
public interface IDiscordClient : IDisposable
11+
public interface IDiscordClient : IDisposable, IAsyncDisposable
1212
{
1313
/// <summary>
1414
/// Gets the current state of connection.

src/Discord.Net.Rest/BaseDiscordClient.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,24 @@ internal virtual void Dispose(bool disposing)
149149
_isDisposed = true;
150150
}
151151
}
152+
153+
internal virtual async ValueTask DisposeAsync(bool disposing)
154+
{
155+
if (!_isDisposed)
156+
{
157+
#pragma warning disable IDISP007
158+
await ApiClient.DisposeAsync().ConfigureAwait(false);
159+
#pragma warning restore IDISP007
160+
_stateLock?.Dispose();
161+
_isDisposed = true;
162+
}
163+
}
164+
152165
/// <inheritdoc />
153166
public void Dispose() => Dispose(true);
154167

168+
public ValueTask DisposeAsync() => DisposeAsync(true);
169+
155170
/// <inheritdoc />
156171
public Task<int> GetRecommendedShardCountAsync(RequestOptions options = null)
157172
=> ClientHelper.GetRecommendShardCountAsync(this, options);

src/Discord.Net.Rest/DiscordRestApiClient.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
namespace Discord.API
2323
{
24-
internal class DiscordRestApiClient : IDisposable
24+
internal class DiscordRestApiClient : IDisposable, IAsyncDisposable
2525
{
2626
#region DiscordRestApiClient
2727
private static readonly ConcurrentDictionary<string, Func<BucketIds, BucketId>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, BucketId>>();
@@ -97,8 +97,29 @@ internal virtual void Dispose(bool disposing)
9797
_isDisposed = true;
9898
}
9999
}
100+
101+
internal virtual async ValueTask DisposeAsync(bool disposing)
102+
{
103+
if (!_isDisposed)
104+
{
105+
if (disposing)
106+
{
107+
_loginCancelToken?.Dispose();
108+
RestClient?.Dispose();
109+
110+
if (!(RequestQueue is null))
111+
await RequestQueue.DisposeAsync().ConfigureAwait(false);
112+
113+
_stateLock?.Dispose();
114+
}
115+
_isDisposed = true;
116+
}
117+
}
118+
100119
public void Dispose() => Dispose(true);
101120

121+
public ValueTask DisposeAsync() => DisposeAsync(true);
122+
102123
public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null)
103124
{
104125
await _stateLock.WaitAsync().ConfigureAwait(false);

src/Discord.Net.Rest/DiscordRestClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ internal override void Dispose(bool disposing)
4747
base.Dispose(disposing);
4848
}
4949

50+
internal override async ValueTask DisposeAsync(bool disposing)
51+
{
52+
if (disposing)
53+
await ApiClient.DisposeAsync().ConfigureAwait(false);
54+
55+
base.Dispose(disposing);
56+
}
57+
5058
/// <inheritdoc />
5159
internal override async Task OnLoginAsync(TokenType tokenType, string token)
5260
{

src/Discord.Net.Rest/Net/Queue/RequestQueue.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Newtonsoft.Json.Bson;
12
using System;
23
using System.Collections.Concurrent;
34
#if DEBUG_LIMITS
@@ -10,7 +11,7 @@
1011

1112
namespace Discord.Net.Queue
1213
{
13-
internal class RequestQueue : IDisposable
14+
internal class RequestQueue : IDisposable, IAsyncDisposable
1415
{
1516
public event Func<BucketId, RateLimitInfo?, string, Task> RateLimitTriggered;
1617

@@ -187,13 +188,31 @@ private async Task RunCleanup()
187188
await Task.Delay(60000, _cancelTokenSource.Token).ConfigureAwait(false); //Runs each minute
188189
}
189190
}
190-
catch (OperationCanceledException) { }
191+
catch (TaskCanceledException) { }
191192
catch (ObjectDisposedException) { }
192193
}
193194

194195
public void Dispose()
195196
{
196-
_cancelTokenSource?.Dispose();
197+
if (!(_cancelTokenSource is null))
198+
{
199+
_cancelTokenSource.Cancel();
200+
_cancelTokenSource.Dispose();
201+
_cleanupTask.GetAwaiter().GetResult();
202+
}
203+
_tokenLock?.Dispose();
204+
_clearToken?.Dispose();
205+
_requestCancelTokenSource?.Dispose();
206+
}
207+
208+
public async ValueTask DisposeAsync()
209+
{
210+
if (!(_cancelTokenSource is null))
211+
{
212+
_cancelTokenSource.Cancel();
213+
_cancelTokenSource.Dispose();
214+
await _cleanupTask.ConfigureAwait(false);
215+
}
197216
_tokenLock?.Dispose();
198217
_clearToken?.Dispose();
199218
_requestCancelTokenSource?.Dispose();

src/Discord.Net.WebSocket/DiscordShardedClient.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,25 @@ internal override void Dispose(bool disposing)
568568

569569
base.Dispose(disposing);
570570
}
571+
572+
internal override ValueTask DisposeAsync(bool disposing)
573+
{
574+
if (!_isDisposed)
575+
{
576+
if (disposing)
577+
{
578+
if (_shards != null)
579+
{
580+
foreach (var client in _shards)
581+
client?.Dispose();
582+
}
583+
}
584+
585+
_isDisposed = true;
586+
}
587+
588+
return base.DisposeAsync(disposing);
589+
}
571590
#endregion
572591
}
573592
}

src/Discord.Net.WebSocket/DiscordSocketApiClient.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,39 @@ internal override void Dispose(bool disposing)
124124
_decompressor?.Dispose();
125125
_compressed?.Dispose();
126126
}
127-
_isDisposed = true;
128127
}
129128

130129
base.Dispose(disposing);
131130
}
132131

132+
#if NETSTANDARD2_1
133+
internal override async ValueTask DisposeAsync(bool disposing)
134+
#else
135+
internal override ValueTask DisposeAsync(bool disposing)
136+
#endif
137+
{
138+
if (!_isDisposed)
139+
{
140+
if (disposing)
141+
{
142+
_connectCancelToken?.Dispose();
143+
(WebSocketClient as IDisposable)?.Dispose();
144+
#if NETSTANDARD2_1
145+
if (!(_decompressor is null))
146+
await _decompressor.DisposeAsync().ConfigureAwait(false);
147+
#else
148+
_decompressor?.Dispose();
149+
#endif
150+
}
151+
}
152+
153+
#if NETSTANDARD2_1
154+
await base.DisposeAsync(disposing).ConfigureAwait(false);
155+
#else
156+
return base.DisposeAsync(disposing);
157+
#endif
158+
}
159+
133160
public async Task ConnectAsync()
134161
{
135162
await _stateLock.WaitAsync().ConfigureAwait(false);

src/Discord.Net.WebSocket/DiscordSocketClient.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,27 @@ internal override void Dispose(bool disposing)
213213
base.Dispose(disposing);
214214
}
215215

216+
217+
internal override async ValueTask DisposeAsync(bool disposing)
218+
{
219+
if (!_isDisposed)
220+
{
221+
if (disposing)
222+
{
223+
await StopAsync().ConfigureAwait(false);
224+
225+
if (!(ApiClient is null))
226+
await ApiClient.DisposeAsync().ConfigureAwait(false);
227+
228+
_stateLock?.Dispose();
229+
}
230+
_isDisposed = true;
231+
}
232+
233+
await base.DisposeAsync(disposing).ConfigureAwait(false);
234+
}
235+
236+
/// <inheritdoc />
216237
internal override async Task OnLoginAsync(TokenType tokenType, string token)
217238
{
218239
if (_shardedClient == null && _defaultStickers.Length == 0 && AlwaysDownloadDefaultStickers)

0 commit comments

Comments
 (0)