@@ -42,20 +42,6 @@ import {
42
42
43
43
const minWireVersionForShardedTransactions = 8 ;
44
44
45
- function assertAlive ( session : ClientSession , callback ?: Callback ) : boolean {
46
- if ( session . serverSession == null ) {
47
- const error = new MongoExpiredSessionError ( ) ;
48
- if ( typeof callback === 'function' ) {
49
- callback ( error ) ;
50
- return false ;
51
- }
52
-
53
- throw error ;
54
- }
55
-
56
- return true ;
57
- }
58
-
59
45
/** @public */
60
46
export interface ClientSessionOptions {
61
47
/** Whether causal consistency should be enabled on this session */
@@ -89,6 +75,8 @@ const kSnapshotTime = Symbol('snapshotTime');
89
75
const kSnapshotEnabled = Symbol ( 'snapshotEnabled' ) ;
90
76
/** @internal */
91
77
const kPinnedConnection = Symbol ( 'pinnedConnection' ) ;
78
+ /** @internal Accumulates total number of increments to add to txnNumber when applying session to command */
79
+ const kTxnNumberIncrement = Symbol ( 'txnNumberIncrement' ) ;
92
80
93
81
/** @public */
94
82
export interface EndSessionOptions {
@@ -123,13 +111,15 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
123
111
defaultTransactionOptions : TransactionOptions ;
124
112
transaction : Transaction ;
125
113
/** @internal */
126
- [ kServerSession ] ? : ServerSession ;
114
+ [ kServerSession ] : ServerSession | null ;
127
115
/** @internal */
128
116
[ kSnapshotTime ] ?: Timestamp ;
129
117
/** @internal */
130
118
[ kSnapshotEnabled ] = false ;
131
119
/** @internal */
132
120
[ kPinnedConnection ] ?: Connection ;
121
+ /** @internal */
122
+ [ kTxnNumberIncrement ] : number ;
133
123
134
124
/**
135
125
* Create a client session.
@@ -172,7 +162,10 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
172
162
this . sessionPool = sessionPool ;
173
163
this . hasEnded = false ;
174
164
this . clientOptions = clientOptions ;
175
- this [ kServerSession ] = undefined ;
165
+
166
+ this . explicit = ! ! options . explicit ;
167
+ this [ kServerSession ] = this . explicit ? this . sessionPool . acquire ( ) : null ;
168
+ this [ kTxnNumberIncrement ] = 0 ;
176
169
177
170
this . supports = {
178
171
causalConsistency : options . snapshot !== true && options . causalConsistency !== false
@@ -181,24 +174,29 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
181
174
this . clusterTime = options . initialClusterTime ;
182
175
183
176
this . operationTime = undefined ;
184
- this . explicit = ! ! options . explicit ;
185
177
this . owner = options . owner ;
186
178
this . defaultTransactionOptions = Object . assign ( { } , options . defaultTransactionOptions ) ;
187
179
this . transaction = new Transaction ( ) ;
188
180
}
189
181
190
182
/** The server id associated with this session */
191
183
get id ( ) : ServerSessionId | undefined {
192
- return this . serverSession ?. id ;
184
+ return this [ kServerSession ] ?. id ;
193
185
}
194
186
195
187
get serverSession ( ) : ServerSession {
196
- if ( this [ kServerSession ] == null ) {
197
- this [ kServerSession ] = this . sessionPool . acquire ( ) ;
188
+ let serverSession = this [ kServerSession ] ;
189
+ if ( serverSession == null ) {
190
+ if ( this . explicit ) {
191
+ throw new MongoRuntimeError ( 'Unexpected null serverSession for an explicit session' ) ;
192
+ }
193
+ if ( this . hasEnded ) {
194
+ throw new MongoRuntimeError ( 'Unexpected null serverSession for an ended implicit session' ) ;
195
+ }
196
+ serverSession = this . sessionPool . acquire ( ) ;
197
+ this [ kServerSession ] = serverSession ;
198
198
}
199
-
200
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
201
- return this [ kServerSession ] ! ;
199
+ return serverSession ;
202
200
}
203
201
204
202
/** Whether or not this session is configured for snapshot reads */
@@ -267,9 +265,15 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
267
265
const completeEndSession = ( ) => {
268
266
maybeClearPinnedConnection ( this , finalOptions ) ;
269
267
270
- // release the server session back to the pool
271
- this . sessionPool . release ( this . serverSession ) ;
272
- this [ kServerSession ] = undefined ;
268
+ const serverSession = this [ kServerSession ] ;
269
+ if ( serverSession != null ) {
270
+ // release the server session back to the pool
271
+ this . sessionPool . release ( serverSession ) ;
272
+ // Make sure a new serverSession never makes it on to the ClientSession
273
+ Object . defineProperty ( this , kServerSession , {
274
+ value : ServerSession . clone ( serverSession )
275
+ } ) ;
276
+ }
273
277
274
278
// mark the session as ended, and emit a signal
275
279
this . hasEnded = true ;
@@ -279,7 +283,9 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
279
283
done ( ) ;
280
284
} ;
281
285
282
- if ( this . serverSession && this . inTransaction ( ) ) {
286
+ if ( this . inTransaction ( ) ) {
287
+ // If we've reached endSession and the transaction is still active
288
+ // by default we abort it
283
289
this . abortTransaction ( err => {
284
290
if ( err ) return done ( err ) ;
285
291
completeEndSession ( ) ;
@@ -353,12 +359,16 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
353
359
return this . id . id . buffer . equals ( session . id . id . buffer ) ;
354
360
}
355
361
356
- /** Increment the transaction number on the internal ServerSession */
362
+ /**
363
+ * Increment the transaction number on the internal ServerSession
364
+ *
365
+ * @privateRemarks
366
+ * This helper increments a value stored on the client session that will be
367
+ * added to the serverSession's txnNumber upon applying it to a command.
368
+ * This is because the serverSession is lazily acquired after a connection is obtained
369
+ */
357
370
incrementTransactionNumber ( ) : void {
358
- if ( this . serverSession ) {
359
- this . serverSession . txnNumber =
360
- typeof this . serverSession . txnNumber === 'number' ? this . serverSession . txnNumber + 1 : 0 ;
361
- }
371
+ this [ kTxnNumberIncrement ] += 1 ;
362
372
}
363
373
364
374
/** @returns whether this session is currently in a transaction or not */
@@ -376,7 +386,6 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
376
386
throw new MongoCompatibilityError ( 'Transactions are not allowed with snapshot sessions' ) ;
377
387
}
378
388
379
- assertAlive ( this ) ;
380
389
if ( this . inTransaction ( ) ) {
381
390
throw new MongoTransactionError ( 'Transaction already in progress' ) ;
382
391
}
@@ -627,7 +636,7 @@ function attemptTransaction<TSchema>(
627
636
throw err ;
628
637
}
629
638
630
- if ( session . transaction . isActive ) {
639
+ if ( session . inTransaction ( ) ) {
631
640
return session . abortTransaction ( ) . then ( ( ) => maybeRetryOrThrow ( err ) ) ;
632
641
}
633
642
@@ -641,11 +650,6 @@ function endTransaction(
641
650
commandName : 'abortTransaction' | 'commitTransaction' ,
642
651
callback : Callback < Document >
643
652
) {
644
- if ( ! assertAlive ( session , callback ) ) {
645
- // checking result in case callback was called
646
- return ;
647
- }
648
-
649
653
// handle any initial problematic cases
650
654
const txnState = session . transaction . state ;
651
655
@@ -750,7 +754,6 @@ function endTransaction(
750
754
callback ( error , result ) ;
751
755
}
752
756
753
- // Assumption here that commandName is "commitTransaction" or "abortTransaction"
754
757
if ( session . transaction . recoveryToken ) {
755
758
command . recoveryToken = session . transaction . recoveryToken ;
756
759
}
@@ -832,6 +835,30 @@ export class ServerSession {
832
835
833
836
return idleTimeMinutes > sessionTimeoutMinutes - 1 ;
834
837
}
838
+
839
+ /**
840
+ * @internal
841
+ * Cloning meant to keep a readable reference to the server session data
842
+ * after ClientSession has ended
843
+ */
844
+ static clone ( serverSession : ServerSession ) : Readonly < ServerSession > {
845
+ const arrayBuffer = new ArrayBuffer ( 16 ) ;
846
+ const idBytes = Buffer . from ( arrayBuffer ) ;
847
+ idBytes . set ( serverSession . id . id . buffer ) ;
848
+
849
+ const id = new Binary ( idBytes , serverSession . id . id . sub_type ) ;
850
+
851
+ // Manual prototype construction to avoid modifying the constructor of this class
852
+ return Object . setPrototypeOf (
853
+ {
854
+ id : { id } ,
855
+ lastUse : serverSession . lastUse ,
856
+ txnNumber : serverSession . txnNumber ,
857
+ isDirty : serverSession . isDirty
858
+ } ,
859
+ ServerSession . prototype
860
+ ) ;
861
+ }
835
862
}
836
863
837
864
/**
@@ -944,11 +971,11 @@ export function applySession(
944
971
command : Document ,
945
972
options : CommandOptions
946
973
) : MongoDriverError | undefined {
947
- // TODO: merge this with `assertAlive`, did not want to throw a try/catch here
948
974
if ( session . hasEnded ) {
949
975
return new MongoExpiredSessionError ( ) ;
950
976
}
951
977
978
+ // May acquire serverSession here
952
979
const serverSession = session . serverSession ;
953
980
if ( serverSession == null ) {
954
981
return new MongoRuntimeError ( 'Unable to acquire server session' ) ;
@@ -966,15 +993,16 @@ export function applySession(
966
993
serverSession . lastUse = now ( ) ;
967
994
command . lsid = serverSession . id ;
968
995
969
- // first apply non-transaction-specific sessions data
970
- const inTransaction = session . inTransaction ( ) || isTransactionCommand ( command ) ;
971
- const isRetryableWrite = options ?. willRetryWrite || false ;
996
+ const inTxnOrTxnCommand = session . inTransaction ( ) || isTransactionCommand ( command ) ;
997
+ const isRetryableWrite = ! ! options . willRetryWrite ;
972
998
973
- if ( serverSession . txnNumber && ( isRetryableWrite || inTransaction ) ) {
999
+ if ( isRetryableWrite || inTxnOrTxnCommand ) {
1000
+ serverSession . txnNumber += session [ kTxnNumberIncrement ] ;
1001
+ session [ kTxnNumberIncrement ] = 0 ;
974
1002
command . txnNumber = Long . fromNumber ( serverSession . txnNumber ) ;
975
1003
}
976
1004
977
- if ( ! inTransaction ) {
1005
+ if ( ! inTxnOrTxnCommand ) {
978
1006
if ( session . transaction . state !== TxnState . NO_TRANSACTION ) {
979
1007
session . transaction . transition ( TxnState . NO_TRANSACTION ) ;
980
1008
}
0 commit comments