@@ -24,7 +24,7 @@ import {
24
24
} from '../sdam/server_selection' ;
25
25
import type { Topology } from '../sdam/topology' ;
26
26
import type { ClientSession } from '../sessions' ;
27
- import { Timeout } from '../timeout' ;
27
+ import { TimeoutContext } from '../timeout' ;
28
28
import { squashError , supportsRetryableWrites } from '../utils' ;
29
29
import { AbstractOperation , Aspect } from './operation' ;
30
30
@@ -88,6 +88,12 @@ export async function executeOperation<
88
88
) ;
89
89
}
90
90
91
+ timeoutContext ??= TimeoutContext . create ( {
92
+ serverSelectionTimeoutMS : client . s . options . serverSelectionTimeoutMS ,
93
+ waitQueueTimeoutMS : client . s . options . waitQueueTimeoutMS ,
94
+ timeoutMS : operation . options . timeoutMS
95
+ } ) ;
96
+
91
97
const readPreference = operation . readPreference ?? ReadPreference . primary ;
92
98
const inTransaction = ! ! session ?. inTransaction ( ) ;
93
99
@@ -200,18 +206,31 @@ async function tryOperation<
200
206
selector = readPreference ;
201
207
}
202
208
203
- const timeout = operation . timeoutMS != null ? Timeout . expires ( operation . timeoutMS ) : undefined ;
204
- operation . timeout = timeout ;
205
-
206
209
const server = await topology . selectServer ( selector , {
207
210
session,
208
211
operationName : operation . commandName ,
209
- timeout
212
+ timeoutContext
210
213
} ) ;
211
214
212
- const hasReadAspect = operation . hasAspect ( Aspect . READ_OPERATION ) ;
213
- const hasWriteAspect = operation . hasAspect ( Aspect . WRITE_OPERATION ) ;
214
- const inTransaction = session ?. inTransaction ( ) ?? false ;
215
+ if ( session == null ) {
216
+ // No session also means it is not retryable, early exit
217
+ return await operation . execute ( server , undefined , timeoutContext ) ;
218
+ }
219
+
220
+ if ( ! operation . hasAspect ( Aspect . RETRYABLE ) ) {
221
+ // non-retryable operation, early exit
222
+ try {
223
+ return await operation . execute ( server , session , timeoutContext ) ;
224
+ } finally {
225
+ if ( session ?. owner != null && session . owner === owner ) {
226
+ try {
227
+ await session . endSession ( ) ;
228
+ } catch ( error ) {
229
+ squashError ( error ) ;
230
+ }
231
+ }
232
+ }
233
+ }
215
234
216
235
const willRetryRead = topology . s . options . retryReads && ! inTransaction && operation . canRetryRead ;
217
236
@@ -231,42 +250,16 @@ async function tryOperation<
231
250
session . incrementTransactionNumber ( ) ;
232
251
}
233
252
234
- // TODO(NODE-6231): implement infinite retry within CSOT timeout here
235
- const maxTries = willRetry ? 2 : 1 ;
236
- let previousOperationError : MongoError | undefined ;
237
- let previousServer : ServerDescription | undefined ;
238
-
239
- // TODO(NODE-6231): implement infinite retry within CSOT timeout here
240
- for ( let tries = 0 ; tries < maxTries ; tries ++ ) {
241
- if ( previousOperationError ) {
242
- if ( hasWriteAspect && previousOperationError . code === MMAPv1_RETRY_WRITES_ERROR_CODE ) {
243
- throw new MongoServerError ( {
244
- message : MMAPv1_RETRY_WRITES_ERROR_MESSAGE ,
245
- errmsg : MMAPv1_RETRY_WRITES_ERROR_MESSAGE ,
246
- originalError : previousOperationError
247
- } ) ;
248
- }
249
-
250
- if ( hasWriteAspect && ! isRetryableWriteError ( previousOperationError ) )
251
- throw previousOperationError ;
252
-
253
- if ( hasReadAspect && ! isRetryableReadError ( previousOperationError ) )
254
- throw previousOperationError ;
255
-
256
- if (
257
- previousOperationError instanceof MongoNetworkError &&
258
- operation . hasAspect ( Aspect . CURSOR_CREATING ) &&
259
- session != null &&
260
- session . isPinned &&
261
- ! session . inTransaction ( )
262
- ) {
263
- session . unpin ( { force : true , forceClear : true } ) ;
264
- }
265
-
266
- server = await topology . selectServer ( selector , {
253
+ try {
254
+ return await operation . execute ( server , session , timeoutContext ) ;
255
+ } catch ( operationError ) {
256
+ if ( willRetry && operationError instanceof MongoError ) {
257
+ return await retryOperation ( operation , operationError , {
267
258
session,
268
- operationName : operation . commandName ,
269
- previousServer
259
+ topology,
260
+ selector,
261
+ previousServer : server . description ,
262
+ timeoutContext
270
263
} ) ;
271
264
272
265
if ( hasWriteAspect && ! supportsRetryableWrites ( server ) ) {
@@ -276,18 +269,22 @@ async function tryOperation<
276
269
}
277
270
}
278
271
279
- try {
280
- return await operation . execute ( server , session , timeoutContext ) ;
281
- } catch ( operationError ) {
282
- if ( ! ( operationError instanceof MongoError ) ) throw operationError ;
272
+ /** @internal */
273
+ type RetryOptions = {
274
+ session : ClientSession ;
275
+ topology : Topology ;
276
+ selector : ReadPreference | ServerSelector ;
277
+ previousServer : ServerDescription ;
278
+ timeoutContext : TimeoutContext ;
279
+ } ;
283
280
284
281
async function retryOperation <
285
282
T extends AbstractOperation < TResult > ,
286
283
TResult = ResultTypeFromOperation < T >
287
284
> (
288
285
operation : T ,
289
286
originalError : MongoError ,
290
- { session, topology, selector, previousServer } : RetryOptions
287
+ { session, topology, selector, previousServer, timeoutContext } : RetryOptions
291
288
) : Promise < TResult > {
292
289
const isWriteOperation = operation . hasAspect ( Aspect . WRITE_OPERATION ) ;
293
290
const isReadOperation = operation . hasAspect ( Aspect . READ_OPERATION ) ;
@@ -323,9 +320,9 @@ async function retryOperation<
323
320
// select a new server, and attempt to retry the operation
324
321
const server = await topology . selectServer ( selector , {
325
322
session,
326
- timeout : operation . timeout ,
327
323
operationName : operation . commandName ,
328
- previousServer
324
+ previousServer,
325
+ timeoutContext
329
326
} ) ;
330
327
331
328
if ( isWriteOperation && ! supportsRetryableWrites ( server ) ) {
@@ -335,7 +332,7 @@ async function retryOperation<
335
332
}
336
333
337
334
try {
338
- return await operation . execute ( server , session ) ;
335
+ return await operation . execute ( server , session , timeoutContext ) ;
339
336
} catch ( retryError ) {
340
337
if (
341
338
retryError instanceof MongoError &&
0 commit comments