@@ -20,7 +20,7 @@ import { ReadConcern, type ReadConcernLike } from '../read_concern';
20
20
import { ReadPreference , type ReadPreferenceLike } from '../read_preference' ;
21
21
import { type AsyncDisposable , configureResourceManagement } from '../resource_management' ;
22
22
import type { Server } from '../sdam/server' ;
23
- import { ClientSession , maybeClearPinnedConnection } from '../sessions' ;
23
+ import { type ClientSession , maybeClearPinnedConnection } from '../sessions' ;
24
24
import { type CSOTTimeoutContext , type Timeout , TimeoutContext } from '../timeout' ;
25
25
import {
26
26
addAbortListener ,
@@ -227,7 +227,7 @@ export abstract class AbstractCursor<
227
227
/** @internal */
228
228
private cursorId : Long | null ;
229
229
/** @internal */
230
- private cursorSession : ClientSession ;
230
+ private cursorSession : ClientSession | null ;
231
231
/** @internal */
232
232
private selectedServer ?: Server ;
233
233
/** @internal */
@@ -352,11 +352,7 @@ export abstract class AbstractCursor<
352
352
this . cursorOptions . maxAwaitTimeMS = options . maxAwaitTimeMS ;
353
353
}
354
354
355
- if ( options . session instanceof ClientSession ) {
356
- this . cursorSession = options . session ;
357
- } else {
358
- this . cursorSession = this . cursorClient . startSession ( { owner : this , explicit : false } ) ;
359
- }
355
+ this . cursorSession = options . session ?? null ;
360
356
361
357
this . deserializationOptions = {
362
358
...this . cursorOptions ,
@@ -413,7 +409,7 @@ export abstract class AbstractCursor<
413
409
}
414
410
415
411
/** @internal */
416
- get session ( ) : ClientSession {
412
+ get session ( ) : ClientSession | null {
417
413
return this . cursorSession ;
418
414
}
419
415
@@ -877,11 +873,12 @@ export abstract class AbstractCursor<
877
873
this . trackCursor ( ) ;
878
874
879
875
// We only want to end this session if we created it, and it hasn't ended yet
880
- if ( this . cursorSession . explicit === false ) {
876
+ if ( this . cursorSession ? .explicit === false ) {
881
877
if ( ! this . cursorSession . hasEnded ) {
882
878
this . cursorSession . endSession ( ) . then ( undefined , squashError ) ;
883
879
}
884
- this . cursorSession = this . cursorClient . startSession ( { owner : this , explicit : false } ) ;
880
+
881
+ this . cursorSession = null ;
885
882
}
886
883
}
887
884
@@ -907,6 +904,13 @@ export abstract class AbstractCursor<
907
904
'Unexpected null selectedServer. A cursor creating command should have set this'
908
905
) ;
909
906
}
907
+
908
+ if ( this . cursorSession == null ) {
909
+ throw new MongoRuntimeError (
910
+ 'Unexpected null session. A cursor creating command should have set this'
911
+ ) ;
912
+ }
913
+
910
914
const getMoreOptions = {
911
915
...this . cursorOptions ,
912
916
session : this . cursorSession ,
@@ -941,6 +945,7 @@ export abstract class AbstractCursor<
941
945
) ;
942
946
}
943
947
try {
948
+ this . cursorSession ??= this . cursorClient . startSession ( { owner : this , explicit : false } ) ;
944
949
const state = await this . _initialize ( this . cursorSession ) ;
945
950
// Set omitMaxTimeMS to the value needed for subsequent getMore calls
946
951
this . cursorOptions . omitMaxTimeMS = this . cursorOptions . timeoutMS != null ;
@@ -1032,41 +1037,57 @@ export abstract class AbstractCursor<
1032
1037
return this . timeoutContext ?. refreshed ( ) ;
1033
1038
}
1034
1039
} ;
1035
- try {
1036
- if (
1037
- ! this . isKilled &&
1038
- this . cursorId &&
1039
- ! this . cursorId . isZero ( ) &&
1040
- this . cursorNamespace &&
1041
- this . selectedServer &&
1042
- ! this . cursorSession . hasEnded
1043
- ) {
1044
- this . isKilled = true ;
1045
- const cursorId = this . cursorId ;
1046
- this . cursorId = Long . ZERO ;
1047
-
1048
- await executeOperation (
1049
- this . cursorClient ,
1050
- new KillCursorsOperation ( cursorId , this . cursorNamespace , this . selectedServer , {
1051
- session : this . cursorSession
1052
- } ) ,
1053
- timeoutContextForKillCursors ( )
1054
- ) ;
1040
+
1041
+ const withEmitClose = async ( fn : ( ) => Promise < void > ) => {
1042
+ try {
1043
+ await fn ( ) ;
1044
+ } finally {
1045
+ this . emitClose ( ) ;
1055
1046
}
1056
- } catch ( error ) {
1057
- squashError ( error ) ;
1058
- } finally {
1047
+ } ;
1048
+
1049
+ const close = async ( ) => {
1050
+ // if no session has been defined on the cursor, the cursor was never initialized
1051
+ // or the cursor was re-wound and never re-iterated. In either case, we
1052
+ // 1. do not need to end the session (there is no session after all)
1053
+ // 2. do not need to kill the cursor server-side
1054
+ const session = this . cursorSession ;
1055
+ if ( ! session ) return ;
1056
+
1059
1057
try {
1060
- if ( this . cursorSession ?. owner === this ) {
1061
- await this . cursorSession . endSession ( { error } ) ;
1062
- }
1063
- if ( ! this . cursorSession ?. inTransaction ( ) ) {
1064
- maybeClearPinnedConnection ( this . cursorSession , { error } ) ;
1058
+ if (
1059
+ ! this . isKilled &&
1060
+ this . cursorId &&
1061
+ ! this . cursorId . isZero ( ) &&
1062
+ this . cursorNamespace &&
1063
+ this . selectedServer &&
1064
+ ! session . hasEnded
1065
+ ) {
1066
+ this . isKilled = true ;
1067
+ const cursorId = this . cursorId ;
1068
+ this . cursorId = Long . ZERO ;
1069
+
1070
+ await executeOperation (
1071
+ this . cursorClient ,
1072
+ new KillCursorsOperation ( cursorId , this . cursorNamespace , this . selectedServer , {
1073
+ session
1074
+ } ) ,
1075
+ timeoutContextForKillCursors ( )
1076
+ ) ;
1065
1077
}
1078
+ } catch ( error ) {
1079
+ squashError ( error ) ;
1066
1080
} finally {
1067
- this . emitClose ( ) ;
1081
+ if ( session . owner === this ) {
1082
+ await session . endSession ( { error } ) ;
1083
+ }
1084
+ if ( ! session . inTransaction ( ) ) {
1085
+ maybeClearPinnedConnection ( session , { error } ) ;
1086
+ }
1068
1087
}
1069
- }
1088
+ } ;
1089
+
1090
+ await withEmitClose ( close ) ;
1070
1091
}
1071
1092
1072
1093
/** @internal */
0 commit comments