@@ -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 perform to txnNumber */
79
+ const kTxnNumberIncrement = Symbol ( 'txnNumberIncrement' ) ;
92
80
93
81
/** @public */
94
82
export interface EndSessionOptions {
@@ -130,6 +118,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
130
118
[ kSnapshotEnabled ] = false ;
131
119
/** @internal */
132
120
[ kPinnedConnection ] ?: Connection ;
121
+ /** @internal Accumulates total number of increments to perform to txnNumber */
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 = Boolean ( options . explicit ) ;
167
+ this [ kServerSession ] = this . explicit ? this . sessionPool . acquire ( ) : undefined ;
168
+ this [ kTxnNumberIncrement ] = 0 ;
176
169
177
170
this . supports = {
178
171
causalConsistency : options . snapshot !== true && options . causalConsistency !== false
@@ -181,24 +174,27 @@ 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
+ const serverSession = this [ kServerSession ] ;
185
+ if ( serverSession == null ) {
186
+ return undefined ;
187
+ }
188
+ return serverSession . id ;
193
189
}
194
190
195
191
get serverSession ( ) : ServerSession {
196
- if ( this [ kServerSession ] == null ) {
197
- this [ kServerSession ] = this . sessionPool . acquire ( ) ;
192
+ let serverSession = this [ kServerSession ] ;
193
+ if ( serverSession == null ) {
194
+ serverSession = this . sessionPool . acquire ( ) ;
195
+ this [ kServerSession ] = serverSession ;
198
196
}
199
-
200
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
201
- return this [ kServerSession ] ! ;
197
+ return serverSession ;
202
198
}
203
199
204
200
/** Whether or not this session is configured for snapshot reads */
@@ -267,9 +263,15 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
267
263
const completeEndSession = ( ) => {
268
264
maybeClearPinnedConnection ( this , finalOptions ) ;
269
265
270
- // release the server session back to the pool
271
- this . sessionPool . release ( this . serverSession ) ;
272
- this [ kServerSession ] = undefined ;
266
+ const serverSession = this [ kServerSession ] ;
267
+ if ( serverSession != null ) {
268
+ // release the server session back to the pool
269
+ this . sessionPool . release ( serverSession ) ;
270
+ // Make sure a new serverSession never makes it on to the ClientSession
271
+ Object . defineProperty ( this , kServerSession , {
272
+ value : ServerSession . clone ( serverSession )
273
+ } ) ;
274
+ }
273
275
274
276
// mark the session as ended, and emit a signal
275
277
this . hasEnded = true ;
@@ -279,7 +281,9 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
279
281
done ( ) ;
280
282
} ;
281
283
282
- if ( this . serverSession && this . inTransaction ( ) ) {
284
+ if ( this . inTransaction ( ) ) {
285
+ // If we've reached endSession and the transaction is still active
286
+ // by default we abort it
283
287
this . abortTransaction ( err => {
284
288
if ( err ) return done ( err ) ;
285
289
completeEndSession ( ) ;
@@ -355,10 +359,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
355
359
356
360
/** Increment the transaction number on the internal ServerSession */
357
361
incrementTransactionNumber ( ) : void {
358
- if ( this . serverSession ) {
359
- this . serverSession . txnNumber =
360
- typeof this . serverSession . txnNumber === 'number' ? this . serverSession . txnNumber + 1 : 0 ;
361
- }
362
+ this [ kTxnNumberIncrement ] += 1 ;
362
363
}
363
364
364
365
/** @returns whether this session is currently in a transaction or not */
@@ -376,7 +377,6 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
376
377
throw new MongoCompatibilityError ( 'Transactions are not allowed with snapshot sessions' ) ;
377
378
}
378
379
379
- assertAlive ( this ) ;
380
380
if ( this . inTransaction ( ) ) {
381
381
throw new MongoTransactionError ( 'Transaction already in progress' ) ;
382
382
}
@@ -627,7 +627,7 @@ function attemptTransaction<TSchema>(
627
627
throw err ;
628
628
}
629
629
630
- if ( session . transaction . isActive ) {
630
+ if ( session . inTransaction ( ) ) {
631
631
return session . abortTransaction ( ) . then ( ( ) => maybeRetryOrThrow ( err ) ) ;
632
632
}
633
633
@@ -641,11 +641,6 @@ function endTransaction(
641
641
commandName : 'abortTransaction' | 'commitTransaction' ,
642
642
callback : Callback < Document >
643
643
) {
644
- if ( ! assertAlive ( session , callback ) ) {
645
- // checking result in case callback was called
646
- return ;
647
- }
648
-
649
644
// handle any initial problematic cases
650
645
const txnState = session . transaction . state ;
651
646
@@ -750,7 +745,6 @@ function endTransaction(
750
745
callback ( error , result ) ;
751
746
}
752
747
753
- // Assumption here that commandName is "commitTransaction" or "abortTransaction"
754
748
if ( session . transaction . recoveryToken ) {
755
749
command . recoveryToken = session . transaction . recoveryToken ;
756
750
}
@@ -832,6 +826,30 @@ export class ServerSession {
832
826
833
827
return idleTimeMinutes > sessionTimeoutMinutes - 1 ;
834
828
}
829
+
830
+ /**
831
+ * @internal
832
+ * Cloning meant to keep a readable reference to the server session data
833
+ * after ClientSession has ended
834
+ */
835
+ static clone ( serverSession : ServerSession ) : Readonly < ServerSession > {
836
+ const arrayBuffer = new ArrayBuffer ( 16 ) ;
837
+ const idBytes = Buffer . from ( arrayBuffer ) ;
838
+ idBytes . set ( serverSession . id . id . buffer ) ;
839
+
840
+ const id = new Binary ( idBytes , serverSession . id . id . sub_type ) ;
841
+
842
+ // Manual prototype construction to avoid modifying the constructor of this class
843
+ return Object . setPrototypeOf (
844
+ {
845
+ id : { id } ,
846
+ lastUse : serverSession . lastUse ,
847
+ txnNumber : serverSession . txnNumber ,
848
+ isDirty : serverSession . isDirty
849
+ } ,
850
+ ServerSession . prototype
851
+ ) ;
852
+ }
835
853
}
836
854
837
855
/**
@@ -944,11 +962,11 @@ export function applySession(
944
962
command : Document ,
945
963
options : CommandOptions
946
964
) : MongoDriverError | undefined {
947
- // TODO: merge this with `assertAlive`, did not want to throw a try/catch here
948
965
if ( session . hasEnded ) {
949
966
return new MongoExpiredSessionError ( ) ;
950
967
}
951
968
969
+ // May acquire serverSession here
952
970
const serverSession = session . serverSession ;
953
971
if ( serverSession == null ) {
954
972
return new MongoRuntimeError ( 'Unable to acquire server session' ) ;
@@ -967,14 +985,16 @@ export function applySession(
967
985
command . lsid = serverSession . id ;
968
986
969
987
// first apply non-transaction-specific sessions data
970
- const inTransaction = session . inTransaction ( ) || isTransactionCommand ( command ) ;
971
- const isRetryableWrite = options ? .willRetryWrite || false ;
988
+ const inTxnOrTxnCommand = session . inTransaction ( ) || isTransactionCommand ( command ) ;
989
+ const isRetryableWrite = Boolean ( options . willRetryWrite ) ;
972
990
973
- if ( serverSession . txnNumber && ( isRetryableWrite || inTransaction ) ) {
991
+ if ( isRetryableWrite || inTxnOrTxnCommand ) {
992
+ serverSession . txnNumber += session [ kTxnNumberIncrement ] ;
993
+ session [ kTxnNumberIncrement ] = 0 ;
974
994
command . txnNumber = Long . fromNumber ( serverSession . txnNumber ) ;
975
995
}
976
996
977
- if ( ! inTransaction ) {
997
+ if ( ! inTxnOrTxnCommand ) {
978
998
if ( session . transaction . state !== TxnState . NO_TRANSACTION ) {
979
999
session . transaction . transition ( TxnState . NO_TRANSACTION ) ;
980
1000
}
0 commit comments