@@ -20,6 +20,7 @@ import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
20
20
import { type AsyncDisposable , configureResourceManagement } from '../resource_management' ;
21
21
import type { Server } from '../sdam/server' ;
22
22
import { ClientSession , maybeClearPinnedConnection } from '../sessions' ;
23
+ import { TimeoutContext } from '../timeout' ;
23
24
import { type MongoDBNamespace , squashError } from '../utils' ;
24
25
25
26
/**
@@ -59,11 +60,15 @@ export interface CursorStreamOptions {
59
60
/** @public */
60
61
export type CursorFlag = ( typeof CURSOR_FLAGS ) [ number ] ;
61
62
63
+ /** @internal */
62
64
export const CursorTimeoutMode = Object . freeze ( {
63
65
ITERATION : 'iteration' ,
64
66
LIFETIME : 'lifetime'
65
67
} as const ) ;
66
68
69
+ /** @internal
70
+ * TODO(NODE-5688): Document and release
71
+ * */
67
72
export type CursorTimeoutMode = ( typeof CursorTimeoutMode ) [ keyof typeof CursorTimeoutMode ] ;
68
73
69
74
/** @public */
@@ -111,6 +116,7 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
111
116
noCursorTimeout ?: boolean ;
112
117
/** @internal TODO(NODE-5688): make this public */
113
118
timeoutMS ?: number ;
119
+ /** @internal TODO(NODE-5688): make this public */
114
120
timeoutMode ?: CursorTimeoutMode ;
115
121
}
116
122
@@ -131,6 +137,12 @@ export type AbstractCursorEvents = {
131
137
[ AbstractCursor . CLOSE ] ( ) : void ;
132
138
} ;
133
139
140
+ /** @internal */
141
+ export type CursorInitializeOptions = {
142
+ omitMaxTimeMS ?: boolean ;
143
+ timeoutContext ?: TimeoutContext ;
144
+ } ;
145
+
134
146
/** @public */
135
147
export abstract class AbstractCursor <
136
148
TSchema = any ,
@@ -161,6 +173,8 @@ export abstract class AbstractCursor<
161
173
private isKilled : boolean ;
162
174
/** @internal */
163
175
protected readonly cursorOptions : InternalAbstractCursorOptions ;
176
+ /** @internal */
177
+ protected timeoutContext ?: TimeoutContext ;
164
178
165
179
/** @event */
166
180
static readonly CLOSE = 'close' as const ;
@@ -191,11 +205,7 @@ export abstract class AbstractCursor<
191
205
} ;
192
206
this . cursorOptions . timeoutMS = options . timeoutMS ;
193
207
if ( this . cursorOptions . timeoutMS != null ) {
194
- this . cursorOptions . timeoutMode = options . timeoutMode ;
195
- }
196
-
197
- if ( this . cursorOptions . timeoutMS != null ) {
198
- if ( this . cursorOptions . timeoutMode == null ) {
208
+ if ( options . timeoutMode == null ) {
199
209
if ( this . cursorOptions . tailable ) {
200
210
this . cursorOptions . timeoutMode = CursorTimeoutMode . ITERATION ;
201
211
} else {
@@ -442,6 +452,9 @@ export abstract class AbstractCursor<
442
452
await this . fetchBatch ( ) ;
443
453
} while ( ! this . isDead || ( this . documents ?. length ?? 0 ) !== 0 ) ;
444
454
455
+ if ( this . cursorOptions . timeoutMode === 'iteration' ) {
456
+ this . timeoutContext ?. refresh ( ) ;
457
+ }
445
458
return null ;
446
459
}
447
460
@@ -493,8 +506,8 @@ export abstract class AbstractCursor<
493
506
/**
494
507
* Frees any client-side resources used by the cursor.
495
508
*/
496
- async close ( ) : Promise < void > {
497
- await this . cleanup ( ) ;
509
+ async close ( timeoutMS ?: number ) : Promise < void > {
510
+ await this . cleanup ( timeoutMS ) ;
498
511
}
499
512
500
513
/**
@@ -699,7 +712,8 @@ export abstract class AbstractCursor<
699
712
700
713
/** @internal */
701
714
protected abstract _initialize (
702
- session : ClientSession | undefined
715
+ session : ClientSession | undefined ,
716
+ options ?: CursorInitializeOptions
703
717
) : Promise < InitialCursorResponse > ;
704
718
705
719
/** @internal */
@@ -721,11 +735,12 @@ export abstract class AbstractCursor<
721
735
{
722
736
...this . cursorOptions ,
723
737
session : this . cursorSession ,
724
- batchSize
738
+ batchSize,
739
+ omitMaxTimeMS : this . cursorOptions . timeoutMode != null
725
740
}
726
741
) ;
727
742
728
- return await executeOperation ( this . cursorClient , getMoreOperation ) ;
743
+ return await executeOperation ( this . cursorClient , getMoreOperation , this . timeoutContext ) ;
729
744
}
730
745
731
746
/**
@@ -736,8 +751,22 @@ export abstract class AbstractCursor<
736
751
* a significant refactor.
737
752
*/
738
753
private async cursorInit ( ) : Promise < void > {
754
+ if ( this . cursorOptions . timeoutMS != null ) {
755
+ this . timeoutContext = TimeoutContext . create ( {
756
+ serverSelectionTimeoutMS : this . client . options . serverSelectionTimeoutMS ,
757
+ timeoutMS : this . cursorOptions . timeoutMS ,
758
+ cursorTimeoutMode : this . cursorOptions . timeoutMode
759
+ } ) ;
760
+ }
761
+ const omitMaxTimeMS =
762
+ this . cursorOptions . timeoutMS != null &&
763
+ ( ( this . cursorOptions . timeoutMode === 'iteration' && ! this . cursorOptions . tailable ) ||
764
+ ( this . cursorOptions . tailable && ! this . cursorOptions . awaitData ) ) ;
739
765
try {
740
- const state = await this . _initialize ( this . cursorSession ) ;
766
+ const state = await this . _initialize ( this . cursorSession , {
767
+ timeoutContext : this . timeoutContext ,
768
+ omitMaxTimeMS
769
+ } ) ;
741
770
const response = state . response ;
742
771
this . selectedServer = state . server ;
743
772
this . cursorId = response . id ;
@@ -747,7 +776,7 @@ export abstract class AbstractCursor<
747
776
} catch ( error ) {
748
777
// the cursor is now initialized, even if an error occurred
749
778
this . initialized = true ;
750
- await this . cleanup ( error ) ;
779
+ await this . cleanup ( undefined , error ) ;
751
780
throw error ;
752
781
}
753
782
@@ -788,7 +817,7 @@ export abstract class AbstractCursor<
788
817
this . documents = response ;
789
818
} catch ( error ) {
790
819
try {
791
- await this . cleanup ( error ) ;
820
+ await this . cleanup ( undefined , error ) ;
792
821
} catch ( error ) {
793
822
// `cleanupCursor` should never throw, squash and throw the original error
794
823
squashError ( error ) ;
@@ -809,7 +838,7 @@ export abstract class AbstractCursor<
809
838
}
810
839
811
840
/** @internal */
812
- private async cleanup ( error ?: Error ) {
841
+ private async cleanup ( timeoutMS ?: number , error ?: Error ) {
813
842
this . isClosed = true ;
814
843
const session = this . cursorSession ;
815
844
try {
@@ -824,11 +853,22 @@ export abstract class AbstractCursor<
824
853
this . isKilled = true ;
825
854
const cursorId = this . cursorId ;
826
855
this . cursorId = Long . ZERO ;
856
+ let timeoutContext : TimeoutContext | undefined ;
857
+ if ( timeoutMS != null ) {
858
+ timeoutContext = TimeoutContext . create ( {
859
+ serverSelectionTimeoutMS : this . client . options . serverSelectionTimeoutMS ,
860
+ timeoutMS
861
+ } ) ;
862
+ } else {
863
+ this . timeoutContext ?. refresh ( ) ;
864
+ timeoutContext = this . timeoutContext ;
865
+ }
827
866
await executeOperation (
828
867
this . cursorClient ,
829
868
new KillCursorsOperation ( cursorId , this . cursorNamespace , this . selectedServer , {
830
869
session
831
- } )
870
+ } ) ,
871
+ timeoutContext
832
872
) ;
833
873
}
834
874
} catch ( error ) {
0 commit comments