23
23
using MongoDB . Bson . Serialization ;
24
24
using MongoDB . Bson . Serialization . Serializers ;
25
25
using MongoDB . Driver . Core . Bindings ;
26
+ using MongoDB . Driver . Core . Connections ;
26
27
using MongoDB . Driver . Core . Events ;
27
28
using MongoDB . Driver . Core . Misc ;
28
29
using MongoDB . Driver . Core . WireProtocol ;
@@ -48,6 +49,7 @@ public class AsyncCursor<TDocument> : IAsyncCursor<TDocument>
48
49
private readonly int ? _batchSize ;
49
50
private readonly CollectionNamespace _collectionNamespace ;
50
51
private IChannelSource _channelSource ;
52
+ private bool _closed ;
51
53
private int _count ;
52
54
private IReadOnlyList < TDocument > _currentBatch ;
53
55
private long _cursorId ;
@@ -133,6 +135,39 @@ private int CalculateGetMoreProtocolNumberToReturn()
133
135
return numberToReturn ;
134
136
}
135
137
138
+ /// <summary>
139
+ /// Closes the cursor.
140
+ /// </summary>
141
+ /// <param name="cancellationToken">The cancellation token.</param>
142
+ public void Close ( CancellationToken cancellationToken = default ( CancellationToken ) )
143
+ {
144
+ try
145
+ {
146
+ CloseIfNotAlreadyClosed ( cancellationToken ) ;
147
+ }
148
+ finally
149
+ {
150
+ Dispose ( ) ;
151
+ }
152
+ }
153
+
154
+ /// <summary>
155
+ /// Closes the cursor.
156
+ /// </summary>
157
+ /// <param name="cancellationToken">The cancellation token.</param>
158
+ /// <returns>A task.</returns>
159
+ public async Task CloseAsync ( CancellationToken cancellationToken = default ( CancellationToken ) )
160
+ {
161
+ try
162
+ {
163
+ await CloseIfNotAlreadyClosedAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
164
+ }
165
+ finally
166
+ {
167
+ Dispose ( ) ;
168
+ }
169
+ }
170
+
136
171
private CursorBatch < TDocument > CreateCursorBatch ( BsonDocument result )
137
172
{
138
173
var cursorDocument = result [ "cursor" ] . AsBsonDocument ;
@@ -159,6 +194,17 @@ private BsonDocument CreateGetMoreCommand()
159
194
return command ;
160
195
}
161
196
197
+ private BsonDocument CreateKillCursorsCommand ( )
198
+ {
199
+ var command = new BsonDocument
200
+ {
201
+ { "killCursors" , _collectionNamespace . CollectionName } ,
202
+ { "cursors" , new BsonArray { _cursorId } }
203
+ } ;
204
+
205
+ return command ;
206
+ }
207
+
162
208
private CursorBatch < TDocument > ExecuteGetMoreCommand ( IChannelHandle channel , CancellationToken cancellationToken )
163
209
{
164
210
var command = CreateGetMoreCommand ( ) ;
@@ -227,6 +273,47 @@ private Task<CursorBatch<TDocument>> ExecuteGetMoreProtocolAsync(IChannelHandle
227
273
cancellationToken ) ;
228
274
}
229
275
276
+ private void ExecuteKillCursorsCommand ( IChannelHandle channel , CancellationToken cancellationToken )
277
+ {
278
+ var command = CreateKillCursorsCommand ( ) ;
279
+ var result = channel . Command (
280
+ _channelSource . Session ,
281
+ null , // readPreference
282
+ _collectionNamespace . DatabaseNamespace ,
283
+ command ,
284
+ null , // commandPayloads
285
+ NoOpElementNameValidator . Instance ,
286
+ null , // additionalOptions
287
+ null , // postWriteAction
288
+ CommandResponseHandling . Return ,
289
+ BsonDocumentSerializer . Instance ,
290
+ _messageEncoderSettings ,
291
+ cancellationToken ) ;
292
+
293
+ ThrowIfKillCursorsCommandFailed ( result , channel . ConnectionDescription . ConnectionId ) ;
294
+ }
295
+
296
+ private async Task ExecuteKillCursorsCommandAsync ( IChannelHandle channel , CancellationToken cancellationToken )
297
+ {
298
+ var command = CreateKillCursorsCommand ( ) ;
299
+ var result = await channel . CommandAsync (
300
+ _channelSource . Session ,
301
+ null , // readPreference
302
+ _collectionNamespace . DatabaseNamespace ,
303
+ command ,
304
+ null , // commandPayloads
305
+ NoOpElementNameValidator . Instance ,
306
+ null , // additionalOptions
307
+ null , // postWriteAction
308
+ CommandResponseHandling . Return ,
309
+ BsonDocumentSerializer . Instance ,
310
+ _messageEncoderSettings ,
311
+ cancellationToken )
312
+ . ConfigureAwait ( false ) ;
313
+
314
+ ThrowIfKillCursorsCommandFailed ( result , channel . ConnectionDescription . ConnectionId ) ;
315
+ }
316
+
230
317
private void ExecuteKillCursorsProtocol ( IChannelHandle channel , CancellationToken cancellationToken )
231
318
{
232
319
channel . KillCursors (
@@ -235,6 +322,14 @@ private void ExecuteKillCursorsProtocol(IChannelHandle channel, CancellationToke
235
322
cancellationToken ) ;
236
323
}
237
324
325
+ private Task ExecuteKillCursorsProtocolAsync ( IChannelHandle channel , CancellationToken cancellationToken )
326
+ {
327
+ return channel . KillCursorsAsync (
328
+ new [ ] { _cursorId } ,
329
+ _messageEncoderSettings ,
330
+ cancellationToken ) ;
331
+ }
332
+
238
333
/// <inheritdoc/>
239
334
public void Dispose ( )
240
335
{
@@ -252,27 +347,66 @@ protected virtual void Dispose(bool disposing)
252
347
{
253
348
if ( ! _disposed )
254
349
{
255
- try
350
+ CloseIfNotAlreadyClosedFromDispose ( ) ;
351
+
352
+ if ( _channelSource != null )
256
353
{
257
- if ( _cursorId != 0 )
258
- {
259
- using ( var source = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) )
260
- {
261
- KillCursor ( _cursorId , source . Token ) ;
262
- }
263
- }
354
+ _channelSource . Dispose ( ) ;
264
355
}
265
- catch
356
+ _disposed = true ;
357
+ }
358
+ }
359
+ }
360
+
361
+ private void CloseIfNotAlreadyClosed ( CancellationToken cancellationToken )
362
+ {
363
+ if ( ! _closed )
364
+ {
365
+ try
366
+ {
367
+ if ( _cursorId != 0 )
266
368
{
267
- // ignore exceptions
369
+ KillCursors ( cancellationToken ) ;
268
370
}
269
- if ( _channelSource != null )
371
+ }
372
+ finally
373
+ {
374
+ _closed = true ;
375
+ }
376
+ }
377
+ }
378
+
379
+ private async Task CloseIfNotAlreadyClosedAsync ( CancellationToken cancellationToken )
380
+ {
381
+ if ( ! _closed )
382
+ {
383
+ try
384
+ {
385
+ if ( _cursorId != 0 )
270
386
{
271
- _channelSource . Dispose ( ) ;
387
+ await KillCursorsAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
272
388
}
273
389
}
390
+ finally
391
+ {
392
+ _closed = true ;
393
+ }
394
+ }
395
+ }
396
+
397
+ private void CloseIfNotAlreadyClosedFromDispose ( )
398
+ {
399
+ try
400
+ {
401
+ using ( var source = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) )
402
+ {
403
+ CloseIfNotAlreadyClosed ( source . Token ) ;
404
+ }
405
+ }
406
+ catch
407
+ {
408
+ // ignore any exceptions from CloseIfNotAlreadyClosed when called from Dispose
274
409
}
275
- _disposed = true ;
276
410
}
277
411
278
412
private void DisposeChannelSourceIfNoLongerNeeded ( )
@@ -316,21 +450,39 @@ private async Task<CursorBatch<TDocument>> GetNextBatchAsync(CancellationToken c
316
450
}
317
451
}
318
452
319
- private void KillCursor ( long cursorId , CancellationToken cancellationToken )
453
+ private void KillCursors ( CancellationToken cancellationToken )
320
454
{
321
- try
455
+ using ( EventContext . BeginOperation ( _operationId ) )
456
+ using ( EventContext . BeginKillCursors ( _collectionNamespace ) )
457
+ using ( var cancellationTokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) )
458
+ using ( var channel = _channelSource . GetChannel ( cancellationTokenSource . Token ) )
322
459
{
323
- using ( EventContext . BeginOperation ( _operationId ) )
324
- using ( EventContext . BeginKillCursors ( _collectionNamespace ) )
325
- using ( var cancellationTokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) )
326
- using ( var channel = _channelSource . GetChannel ( cancellationTokenSource . Token ) )
460
+ if ( Feature . KillCursorsCommand . IsSupported ( channel . ConnectionDescription . ServerVersion ) )
461
+ {
462
+ ExecuteKillCursorsCommand ( channel , cancellationToken ) ;
463
+ }
464
+ else
327
465
{
328
466
ExecuteKillCursorsProtocol ( channel , cancellationToken ) ;
329
467
}
330
468
}
331
- catch
469
+ }
470
+
471
+ private async Task KillCursorsAsync ( CancellationToken cancellationToken )
472
+ {
473
+ using ( EventContext . BeginOperation ( _operationId ) )
474
+ using ( EventContext . BeginKillCursors ( _collectionNamespace ) )
475
+ using ( var cancellationTokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) )
476
+ using ( var channel = await _channelSource . GetChannelAsync ( cancellationTokenSource . Token ) . ConfigureAwait ( false ) )
332
477
{
333
- // ignore exceptions
478
+ if ( Feature . KillCursorsCommand . IsSupported ( channel . ConnectionDescription . ServerVersion ) )
479
+ {
480
+ await ExecuteKillCursorsCommandAsync ( channel , cancellationToken ) . ConfigureAwait ( false ) ;
481
+ }
482
+ else
483
+ {
484
+ await ExecuteKillCursorsProtocolAsync ( channel , cancellationToken ) . ConfigureAwait ( false ) ;
485
+ }
334
486
}
335
487
}
336
488
@@ -393,6 +545,28 @@ private void ThrowIfDisposed()
393
545
}
394
546
}
395
547
548
+ private void ThrowIfKillCursorsCommandFailed ( BsonDocument commandResult , ConnectionId connectionId )
549
+ {
550
+ if ( ! commandResult . GetValue ( "ok" , false ) . ToBoolean ( ) )
551
+ {
552
+ throw new MongoCommandException ( connectionId , "Kill cursors command returned an error." , commandResult ) ;
553
+ }
554
+ else
555
+ {
556
+ var notFoundCursors = commandResult [ "cursorsNotFound" ] . AsBsonArray ;
557
+ if ( notFoundCursors . Count > 0 )
558
+ {
559
+ throw new MongoCursorNotFoundException ( connectionId , _cursorId , commandResult ) ;
560
+ }
561
+
562
+ var killedCursors = commandResult [ "cursorsKilled" ] . AsBsonArray . Select ( c => c . ToInt64 ( ) ) ;
563
+ if ( ! killedCursors . Contains ( _cursorId ) )
564
+ {
565
+ throw new MongoCommandException ( connectionId , "Kill cursors command failed." , commandResult ) ;
566
+ }
567
+ }
568
+ }
569
+
396
570
private bool TryMoveNext ( out bool hasMore )
397
571
{
398
572
hasMore = false ;
@@ -419,4 +593,4 @@ private bool TryMoveNext(out bool hasMore)
419
593
return false ;
420
594
}
421
595
}
422
- }
596
+ }
0 commit comments