1
1
/* Anything javascript specific relating to timeouts */
2
+ import { setTimeout } from 'node:timers/promises' ;
3
+
2
4
import { expect } from 'chai' ;
3
5
import * as semver from 'semver' ;
4
6
import * as sinon from 'sinon' ;
5
7
8
+ import { type CommandSucceededEvent } from '../../../lib/cmap/command_monitoring_events' ;
6
9
import {
7
10
BSON ,
8
11
type ClientSession ,
@@ -326,13 +329,14 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => {
326
329
let client : MongoClient ;
327
330
let internalClient : MongoClient ;
328
331
let commandStarted : CommandStartedEvent [ ] ;
332
+ let commandSucceeded : CommandSucceededEvent [ ] ;
329
333
const failpoint : FailPoint = {
330
334
configureFailPoint : 'failCommand' ,
331
335
mode : 'alwaysOn' ,
332
336
data : {
333
- failCommands : [ 'find' ] ,
337
+ failCommands : [ 'find' , 'getMore' ] ,
334
338
blockConnection : true ,
335
- blockTimeMS : 30
339
+ blockTimeMS : 25
336
340
}
337
341
} ;
338
342
@@ -350,9 +354,11 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => {
350
354
351
355
await internalClient . db ( ) . admin ( ) . command ( failpoint ) ;
352
356
353
- client = this . configuration . newClient ( undefined , { timeoutMS : 20 , monitorCommands : true } ) ;
357
+ client = this . configuration . newClient ( undefined , { monitorCommands : true } ) ;
354
358
commandStarted = [ ] ;
359
+ commandSucceeded = [ ] ;
355
360
client . on ( 'commandStarted' , ev => commandStarted . push ( ev ) ) ;
361
+ client . on ( 'commandSucceeded' , ev => commandSucceeded . push ( ev ) ) ;
356
362
} ) ;
357
363
358
364
afterEach ( async function ( ) {
@@ -365,12 +371,12 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => {
365
371
} ) ;
366
372
367
373
context ( 'ITERATION mode' , ( ) => {
368
- context ( 'when executing a valid operation' , ( ) => {
374
+ context ( 'when executing an operation' , ( ) => {
369
375
it ( 'must apply the configured timeoutMS to the initial operation execution' , async function ( ) {
370
376
const cursor = client
371
377
. db ( 'db' )
372
378
. collection ( 'coll' )
373
- . find ( { } , { batchSize : 3 , timeoutMode : 'iteration' } )
379
+ . find ( { } , { batchSize : 3 , timeoutMode : 'iteration' , timeoutMS : 10 } )
374
380
. limit ( 3 ) ;
375
381
376
382
const maybeError = await cursor . next ( ) . then (
@@ -385,25 +391,26 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => {
385
391
const cursor = client
386
392
. db ( 'db' )
387
393
. collection ( 'coll' )
388
- . find ( { } , { batchSize : 1 , timeoutMode : 'iteration' } )
389
- . project ( { _id : 0 } )
390
- . limit ( 2 ) ;
394
+ . find ( { } , { batchSize : 1 , timeoutMode : 'iteration' , timeoutMS : 50 } )
395
+ . project ( { _id : 0 } ) ;
391
396
392
- const firstDoc = await cursor . next ( ) ;
393
- expect ( firstDoc ) . to . deep . equal ( { x : 1 } ) ;
397
+ // Iterating over 3 documents in the collection, each artificially taking ~25 ms due to failpoint. If timeoutMS is not refreshed, then we'd expect to error
398
+ for await ( const doc of cursor ) {
399
+ expect ( doc ) . to . deep . equal ( { x : 1 } ) ;
400
+ }
394
401
395
- const maybeError = await cursor . next ( ) . then (
396
- ( ) => null ,
397
- e => e
398
- ) ;
402
+ const finds = commandSucceeded . filter ( ev => ev . commandName === 'find' ) ;
403
+ const getMores = commandSucceeded . filter ( ev => ev . commandName === 'getMore' ) ;
399
404
400
- expect ( maybeError ) . to . be . instanceOf ( MongoOperationTimeoutError ) ;
405
+ expect ( finds ) . to . have . length ( 1 ) ; // Expecting 1 find
406
+ expect ( getMores ) . to . have . length ( 3 ) ; // Expecting 3 getMores (including final empty getMore)
401
407
} ) ;
408
+
402
409
it ( 'does not append a maxTimeMS to the original command or getMores' , async function ( ) {
403
410
const cursor = client
404
411
. db ( 'db' )
405
412
. collection ( 'coll' )
406
- . find ( { } , { batchSize : 1 , timeoutMode : 'iteration' } )
413
+ . find ( { } , { batchSize : 1 , timeoutMode : 'iteration' , timeoutMS : 100 } )
407
414
. project ( { _id : 0 } ) ;
408
415
await cursor . toArray ( ) ;
409
416
@@ -422,17 +429,116 @@ describe('CSOT driver tests', { requires: { mongodb: '>=4.4' } }, () => {
422
429
} ) ;
423
430
424
431
context ( 'LIFETIME mode' , ( ) => {
432
+ let client : MongoClient ;
433
+ let internalClient : MongoClient ;
434
+ let commandStarted : CommandStartedEvent [ ] ;
435
+ let commandSucceeded : CommandSucceededEvent [ ] ;
436
+ const failpoint : FailPoint = {
437
+ configureFailPoint : 'failCommand' ,
438
+ mode : 'alwaysOn' ,
439
+ data : {
440
+ failCommands : [ 'find' , 'getMore' ] ,
441
+ blockConnection : true ,
442
+ blockTimeMS : 25
443
+ }
444
+ } ;
445
+
446
+ beforeEach ( async function ( ) {
447
+ internalClient = this . configuration . newClient ( undefined ) ;
448
+ await internalClient . db ( 'db' ) . dropCollection ( 'coll' ) ;
449
+ await internalClient
450
+ . db ( 'db' )
451
+ . collection ( 'coll' )
452
+ . insertMany (
453
+ Array . from ( { length : 3 } , ( ) => {
454
+ return { x : 1 } ;
455
+ } )
456
+ ) ;
457
+
458
+ await internalClient . db ( ) . admin ( ) . command ( failpoint ) ;
459
+
460
+ client = this . configuration . newClient ( undefined , { monitorCommands : true } ) ;
461
+ commandStarted = [ ] ;
462
+ commandSucceeded = [ ] ;
463
+ client . on ( 'commandStarted' , ev => commandStarted . push ( ev ) ) ;
464
+ client . on ( 'commandSucceeded' , ev => commandSucceeded . push ( ev ) ) ;
465
+ } ) ;
466
+
467
+ afterEach ( async function ( ) {
468
+ await internalClient
469
+ . db ( )
470
+ . admin ( )
471
+ . command ( { ...failpoint , mode : 'off' } ) ;
472
+ await internalClient . close ( ) ;
473
+ await client . close ( ) ;
474
+ } ) ;
425
475
context ( 'when executing a next call' , ( ) => {
426
476
context (
427
477
'when there are documents available from previously retrieved batch and timeout has expired' ,
428
478
( ) => {
429
- it ( 'returns documents without error' ) ;
479
+ it ( 'returns documents without error' , async function ( ) {
480
+ const cursor = client
481
+ . db ( 'db' )
482
+ . collection ( 'coll' )
483
+ . find ( { } , { timeoutMode : 'cursorLifetime' , timeoutMS : 50 } )
484
+ . project ( { _id : 0 } ) ;
485
+ const doc = await cursor . next ( ) ;
486
+ expect ( doc ) . to . deep . equal ( { x : 1 } ) ;
487
+ expect ( cursor . documents . length ) . to . be . gt ( 0 ) ;
488
+
489
+ await setTimeout ( 50 ) ;
490
+
491
+ const docOrErr = await cursor . next ( ) . then (
492
+ d => d ,
493
+ e => e
494
+ ) ;
495
+
496
+ expect ( docOrErr ) . to . not . be . instanceOf ( MongoOperationTimeoutError ) ;
497
+ expect ( docOrErr ) . to . be . deep . equal ( { x : 1 } ) ;
498
+ } ) ;
430
499
}
431
500
) ;
432
501
context ( 'when a getMore is required and the timeout has expired' , ( ) => {
433
- it ( 'throws a MongoOperationTimeoutError' ) ;
502
+ it ( 'throws a MongoOperationTimeoutError' , async function ( ) {
503
+ const cursor = client
504
+ . db ( 'db' )
505
+ . collection ( 'coll' )
506
+ . find ( { } , { batchSize : 1 , timeoutMode : 'cursorLifetime' , timeoutMS : 50 } )
507
+ . project ( { _id : 0 } ) ;
508
+ const doc = await cursor . next ( ) ;
509
+ expect ( doc ) . to . deep . equal ( { x : 1 } ) ;
510
+ expect ( cursor . documents . length ) . to . equal ( 0 ) ;
511
+
512
+ await setTimeout ( 50 ) ;
513
+
514
+ const docOrErr = await cursor . next ( ) . then (
515
+ d => d ,
516
+ e => e
517
+ ) ;
518
+
519
+ expect ( docOrErr ) . to . be . instanceOf ( MongoOperationTimeoutError ) ;
520
+ } ) ;
521
+ } ) ;
522
+
523
+ it ( 'does not apply maxTimeMS to a getMore' , async function ( ) {
524
+ const cursor = client
525
+ . db ( 'db' )
526
+ . collection ( 'coll' )
527
+ . find ( { } , { batchSize : 1 , timeoutMode : 'cursorLifetime' , timeoutMS : 1000 } )
528
+ . project ( { _id : 0 } ) ;
529
+ for await ( const _doc of cursor ) {
530
+ // Ignore _doc
531
+ }
532
+
533
+ const getMores = commandStarted
534
+ . filter ( ev => ev . command . getMore != null )
535
+ . map ( ev => ev . command ) ;
536
+ expect ( getMores . length ) . to . be . gt ( 0 ) ;
537
+
538
+ for ( const getMore of getMores ) {
539
+ expect ( getMore . maxTimeMS ) . to . not . exist ;
540
+ }
434
541
} ) ;
435
- it ( 'does not apply maxTimeMS to a getMore' ) ;
436
542
} ) ;
437
543
} ) ;
438
544
} ) ;
0 commit comments