@@ -33,6 +33,7 @@ public class HubConnectionContext
33
33
34
34
private long _lastSendTimestamp = Stopwatch . GetTimestamp ( ) ;
35
35
private ReadOnlyMemory < byte > _cachedPingMessage ;
36
+ private volatile bool _connectionAborted ;
36
37
37
38
/// <summary>
38
39
/// Initializes a new instance of the <see cref="HubConnectionContext"/> class.
@@ -99,6 +100,12 @@ public virtual ValueTask WriteAsync(HubMessage message, CancellationToken cancel
99
100
return new ValueTask ( WriteSlowAsync ( message ) ) ;
100
101
}
101
102
103
+ if ( _connectionAborted )
104
+ {
105
+ _writeLock . Release ( ) ;
106
+ return default ;
107
+ }
108
+
102
109
// This method should never throw synchronously
103
110
var task = WriteCore ( message ) ;
104
111
@@ -129,6 +136,12 @@ public virtual ValueTask WriteAsync(SerializedHubMessage message, CancellationTo
129
136
return new ValueTask ( WriteSlowAsync ( message ) ) ;
130
137
}
131
138
139
+ if ( _connectionAborted )
140
+ {
141
+ _writeLock . Release ( ) ;
142
+ return default ;
143
+ }
144
+
132
145
// This method should never throw synchronously
133
146
var task = WriteCore ( message ) ;
134
147
@@ -158,6 +171,8 @@ private ValueTask<FlushResult> WriteCore(HubMessage message)
158
171
{
159
172
Log . FailedWritingMessage ( _logger , ex ) ;
160
173
174
+ Abort ( ) ;
175
+
161
176
return new ValueTask < FlushResult > ( new FlushResult ( isCanceled : false , isCompleted : true ) ) ;
162
177
}
163
178
}
@@ -175,6 +190,8 @@ private ValueTask<FlushResult> WriteCore(SerializedHubMessage message)
175
190
{
176
191
Log . FailedWritingMessage ( _logger , ex ) ;
177
192
193
+ Abort ( ) ;
194
+
178
195
return new ValueTask < FlushResult > ( new FlushResult ( isCanceled : false , isCompleted : true ) ) ;
179
196
}
180
197
}
@@ -188,6 +205,8 @@ private async Task CompleteWriteAsync(ValueTask<FlushResult> task)
188
205
catch ( Exception ex )
189
206
{
190
207
Log . FailedWritingMessage ( _logger , ex ) ;
208
+
209
+ Abort ( ) ;
191
210
}
192
211
finally
193
212
{
@@ -201,13 +220,20 @@ private async Task WriteSlowAsync(HubMessage message)
201
220
await _writeLock . WaitAsync ( ) ;
202
221
try
203
222
{
223
+ if ( _connectionAborted )
224
+ {
225
+ return ;
226
+ }
227
+
204
228
// Failed to get the lock immediately when entering WriteAsync so await until it is available
205
229
206
230
await WriteCore ( message ) ;
207
231
}
208
232
catch ( Exception ex )
209
233
{
210
234
Log . FailedWritingMessage ( _logger , ex ) ;
235
+
236
+ Abort ( ) ;
211
237
}
212
238
finally
213
239
{
@@ -219,6 +245,11 @@ private async Task WriteSlowAsync(SerializedHubMessage message)
219
245
{
220
246
try
221
247
{
248
+ if ( _connectionAborted )
249
+ {
250
+ return ;
251
+ }
252
+
222
253
// Failed to get the lock immediately when entering WriteAsync so await until it is available
223
254
await _writeLock . WaitAsync ( ) ;
224
255
@@ -227,6 +258,8 @@ private async Task WriteSlowAsync(SerializedHubMessage message)
227
258
catch ( Exception ex )
228
259
{
229
260
Log . FailedWritingMessage ( _logger , ex ) ;
261
+
262
+ Abort ( ) ;
230
263
}
231
264
finally
232
265
{
@@ -250,13 +283,20 @@ private async Task TryWritePingSlowAsync()
250
283
{
251
284
try
252
285
{
286
+ if ( _connectionAborted )
287
+ {
288
+ return ;
289
+ }
290
+
253
291
await _connectionContext . Transport . Output . WriteAsync ( _cachedPingMessage ) ;
254
292
255
293
Log . SentPing ( _logger ) ;
256
294
}
257
295
catch ( Exception ex )
258
296
{
259
297
Log . FailedWritingMessage ( _logger , ex ) ;
298
+
299
+ Abort ( ) ;
260
300
}
261
301
finally
262
302
{
@@ -293,6 +333,12 @@ private async Task WriteHandshakeResponseAsync(HandshakeResponseMessage message)
293
333
/// </summary>
294
334
public virtual void Abort ( )
295
335
{
336
+ _connectionAborted = true ;
337
+
338
+ // Cancel any current writes or writes that are about to happen and have already gone past the _connectionAborted bool
339
+ // We have to do this outside of the lock otherwise it could hang if the write is observing backpressure
340
+ _connectionContext . Transport . Output . CancelPendingFlush ( ) ;
341
+
296
342
// If we already triggered the token then noop, this isn't thread safe but it's good enough
297
343
// to avoid spawning a new task in the most common cases
298
344
if ( _connectionAbortedTokenSource . IsCancellationRequested )
@@ -423,9 +469,24 @@ internal void Abort(Exception exception)
423
469
internal Task AbortAsync ( )
424
470
{
425
471
Abort ( ) ;
472
+
473
+ // Acquire lock to make sure all writes are completed
474
+ if ( ! _writeLock . Wait ( 0 ) )
475
+ {
476
+ return AbortAsyncSlow ( ) ;
477
+ }
478
+
479
+ _writeLock . Release ( ) ;
426
480
return _abortCompletedTcs . Task ;
427
481
}
428
482
483
+ private async Task AbortAsyncSlow ( )
484
+ {
485
+ await _writeLock . WaitAsync ( ) ;
486
+ _writeLock . Release ( ) ;
487
+ await _abortCompletedTcs . Task ;
488
+ }
489
+
429
490
private void KeepAliveTick ( )
430
491
{
431
492
var timestamp = Stopwatch . GetTimestamp ( ) ;
0 commit comments