From 6dccb3c80c7d993c8821f8f00c2806e733d72aa2 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 29 Oct 2025 10:48:52 -0600 Subject: [PATCH 01/10] misc cleanup to improve readability --- src/sessions.ts | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index 0788ff1538..8990da6de7 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -768,7 +768,7 @@ export class ClientSession if ( fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) && - (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT) + (this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT) ) { continue; } @@ -786,26 +786,26 @@ export class ClientSession await this.commitTransaction(); committed = true; } catch (commitError) { - /* - * Note: a maxTimeMS error will have the MaxTimeMSExpired - * code (50) and can be reported as a top-level error or - * inside writeConcernError, ex. - * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' } - * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } } - */ - if ( - !isMaxTimeMSExpiredError(commitError) && - commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) && - (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT) - ) { - continue; - } - - if ( - commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) && - (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT) - ) { - break; + // If CSOT is enabled, we repeatedly retry until timeoutMS expires. + // If CSOT is not enabled, do we still have time remaining or have we timed out? + if (this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT) { + if ( + !isMaxTimeMSExpiredError(commitError) && + commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) + ) { + /* + * Note: a maxTimeMS error will have the MaxTimeMSExpired + * code (50) and can be reported as a top-level error or + * inside writeConcernError, ex. + * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' } + * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } } + */ + continue; + } + + if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { + break; + } } throw commitError; From 09ec1ed3a4d895f43968c3cd2048082a804a5ad9 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 29 Oct 2025 11:19:13 -0600 Subject: [PATCH 02/10] add exponential backoff to withTransaction retry loop --- src/sessions.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index 8990da6de7..f03e6aa479 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -1,3 +1,5 @@ +import { setTimeout } from 'timers/promises'; + import { Binary, type Document, Long, type Timestamp } from './bson'; import type { CommandOptions, Connection } from './cmap/connection'; import { ConnectionPoolMetrics } from './cmap/metrics'; @@ -776,7 +778,7 @@ export class ClientSession throw fnError; } - while (!committed) { + for (let retry = 0; !committed; ++retry) { try { /* * We will rely on ClientSession.commitTransaction() to @@ -786,9 +788,23 @@ export class ClientSession await this.commitTransaction(); committed = true; } catch (commitError) { + const hasNotTimedOut = + this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT; + + /** + * will the provided backoffMS exceed the withTransaction's deadline? + */ + const willExceedTransactionDeadline = (backoffMS: number) => { + return ( + (this.timeoutContext?.csotEnabled() && + backoffMS > this.timeoutContext.remainingTimeMS) || + now() + backoffMS > startTime + MAX_TIMEOUT + ); + }; + // If CSOT is enabled, we repeatedly retry until timeoutMS expires. // If CSOT is not enabled, do we still have time remaining or have we timed out? - if (this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT) { + if (hasNotTimedOut) { if ( !isMaxTimeMSExpiredError(commitError) && commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) @@ -800,6 +816,19 @@ export class ClientSession * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' } * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } } */ + + const BACKOFF_INITIAL_MS = 1; + const BACKOFF_MAX_MS = 500; + const jitter = Math.random(); + const backoffMS = + jitter * Math.min(BACKOFF_INITIAL_MS * 1.25 ** retry, BACKOFF_MAX_MS); + + if (willExceedTransactionDeadline(backoffMS)) { + break; + } + + await setTimeout(backoffMS); + continue; } From e56e624dccfe9a151d2d8cc2f18acf47f19663f3 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 29 Oct 2025 14:10:01 -0600 Subject: [PATCH 03/10] test --- .../transactions-convenient-api.prose.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts diff --git a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts new file mode 100644 index 0000000000..16a03f8b36 --- /dev/null +++ b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts @@ -0,0 +1,58 @@ +import { expect } from 'chai'; +import { test } from 'mocha'; + +import { type CommandFailedEvent, type MongoClient } from '../../mongodb'; +import { configureFailPoint } from '../../tools/utils'; +import { filterForCommands } from '../shared'; + +describe('Retry Backoff is Enforced', function () { + // Drivers should test that retries within `withTransaction` do not occur immediately. Optionally, set BACKOFF_INITIAL to a + // higher value to decrease flakiness of this test. Configure a fail point that forces 30 retries. Check that the total + // time for all retries exceeded 1.25 seconds. + + let client: MongoClient; + let failures: Array; + + beforeEach(async function () { + client = this.configuration.newClient({}, { monitorCommands: true }); + + failures = []; + client.on('commandFailed', filterForCommands('commitTransaction', failures)); + + await client.connect(); + + await configureFailPoint(this.configuration, { + configureFailPoint: 'failCommand', + mode: { + times: 30 + }, + data: { + failCommands: ['commitTransaction'], + errorCode: 24, + errorLabels: ['UnknownTransactionCommitResult'] + } + }); + }); + + afterEach(async function () { + await client?.close(); + }); + + for (let i = 0; i < 250; ++i) { + test.only('works' + i, async function () { + const start = performance.now(); + + await client.withSession(async s => { + await s.withTransaction(async s => { + await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); + }); + }); + + const end = performance.now(); + + expect(failures).to.have.lengthOf(30); + + expect(end - start).to.be.greaterThan(1250); + }); + } +}); From 0d0b1a3f8a8b636b002e1600fa1171f3bf30567d Mon Sep 17 00:00:00 2001 From: bailey Date: Thu, 30 Oct 2025 09:28:19 -0600 Subject: [PATCH 04/10] failures --- src/sessions.ts | 14 ++++---- .../transactions-convenient-api.prose.test.ts | 36 ++++++++++++------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index f03e6aa479..1a3fb73727 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -731,10 +731,10 @@ export class ClientSession const startTime = this.timeoutContext?.csotEnabled() ? this.timeoutContext.start : now(); let committed = false; - let result: any; + let result: T; try { - while (!committed) { + for (let retry = 0; !committed; ++retry) { this.startTransaction(options); // may throw on error try { @@ -778,7 +778,7 @@ export class ClientSession throw fnError; } - for (let retry = 0; !committed; ++retry) { + while (!committed) { try { /* * We will rely on ClientSession.commitTransaction() to @@ -816,7 +816,10 @@ export class ClientSession * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' } * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } } */ + continue; + } + if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { const BACKOFF_INITIAL_MS = 1; const BACKOFF_MAX_MS = 500; const jitter = Math.random(); @@ -829,10 +832,6 @@ export class ClientSession await setTimeout(backoffMS); - continue; - } - - if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { break; } } @@ -841,6 +840,7 @@ export class ClientSession } } } + return result; } finally { this.timeoutContext = null; diff --git a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts index 16a03f8b36..c763090ca0 100644 --- a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts +++ b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts @@ -5,6 +5,8 @@ import { type CommandFailedEvent, type MongoClient } from '../../mongodb'; import { configureFailPoint } from '../../tools/utils'; import { filterForCommands } from '../shared'; +const COMMIT_FAIL_TIMES = 35; + describe('Retry Backoff is Enforced', function () { // Drivers should test that retries within `withTransaction` do not occur immediately. Optionally, set BACKOFF_INITIAL to a // higher value to decrease flakiness of this test. Configure a fail point that forces 30 retries. Check that the total @@ -24,12 +26,11 @@ describe('Retry Backoff is Enforced', function () { await configureFailPoint(this.configuration, { configureFailPoint: 'failCommand', mode: { - times: 30 + times: COMMIT_FAIL_TIMES }, data: { failCommands: ['commitTransaction'], - errorCode: 24, - errorLabels: ['UnknownTransactionCommitResult'] + errorCode: 24 } }); }); @@ -39,20 +40,29 @@ describe('Retry Backoff is Enforced', function () { }); for (let i = 0; i < 250; ++i) { - test.only('works' + i, async function () { - const start = performance.now(); + test.only( + 'works' + i, + { + requires: { + mongodb: '>=4.4', // failCommand + topology: '!single' // transactions can't run on standalone servers + } + }, + async function () { + const start = performance.now(); - await client.withSession(async s => { - await s.withTransaction(async s => { - await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); + await client.withSession(async s => { + await s.withTransaction(async s => { + await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); + }); }); - }); - const end = performance.now(); + const end = performance.now(); - expect(failures).to.have.lengthOf(30); + expect(failures).to.have.lengthOf(COMMIT_FAIL_TIMES); - expect(end - start).to.be.greaterThan(1250); - }); + expect(end - start).to.be.greaterThan(1500); + } + ); } }); From d933fea37a13d8c30a69bce5f138a4f73228fce0 Mon Sep 17 00:00:00 2001 From: bailey Date: Thu, 30 Oct 2025 11:13:42 -0600 Subject: [PATCH 05/10] increase test run count --- .../transactions-convenient-api.prose.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts index c763090ca0..7f982c4f0b 100644 --- a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts +++ b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts @@ -61,7 +61,7 @@ describe('Retry Backoff is Enforced', function () { expect(failures).to.have.lengthOf(COMMIT_FAIL_TIMES); - expect(end - start).to.be.greaterThan(1500); + expect(end - start).to.be.greaterThan(1250); } ); } From 9d2bb2cef6adfb1cddc92dd77578352cac59b33f Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 18 Nov 2025 14:38:08 -0700 Subject: [PATCH 06/10] Update for changes, test passing --- src/sessions.ts | 4 +- .../transactions-convenient-api.prose.test.ts | 92 ++++++++++--------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index 1a3fb73727..9bb648b292 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -820,11 +820,11 @@ export class ClientSession } if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { - const BACKOFF_INITIAL_MS = 1; + const BACKOFF_INITIAL_MS = 5; const BACKOFF_MAX_MS = 500; const jitter = Math.random(); const backoffMS = - jitter * Math.min(BACKOFF_INITIAL_MS * 1.25 ** retry, BACKOFF_MAX_MS); + jitter * Math.min(BACKOFF_INITIAL_MS * 1.5 ** retry, BACKOFF_MAX_MS); if (willExceedTransactionDeadline(backoffMS)) { break; diff --git a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts index 7f982c4f0b..398cce6317 100644 --- a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts +++ b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts @@ -1,68 +1,72 @@ import { expect } from 'chai'; import { test } from 'mocha'; +import * as sinon from 'sinon'; -import { type CommandFailedEvent, type MongoClient } from '../../mongodb'; -import { configureFailPoint } from '../../tools/utils'; -import { filterForCommands } from '../shared'; +import { type MongoClient } from '../../../src'; +import { configureFailPoint, type FailCommandFailPoint, measureDuration } from '../../tools/utils'; -const COMMIT_FAIL_TIMES = 35; +const failCommand: FailCommandFailPoint = { + configureFailPoint: 'failCommand', + mode: { + times: 13 + }, + data: { + failCommands: ['commitTransaction'], + errorCode: 251 + } +}; describe('Retry Backoff is Enforced', function () { - // Drivers should test that retries within `withTransaction` do not occur immediately. Optionally, set BACKOFF_INITIAL to a - // higher value to decrease flakiness of this test. Configure a fail point that forces 30 retries. Check that the total - // time for all retries exceeded 1.25 seconds. - let client: MongoClient; - let failures: Array; beforeEach(async function () { - client = this.configuration.newClient({}, { monitorCommands: true }); - - failures = []; - client.on('commandFailed', filterForCommands('commitTransaction', failures)); - - await client.connect(); - - await configureFailPoint(this.configuration, { - configureFailPoint: 'failCommand', - mode: { - times: COMMIT_FAIL_TIMES - }, - data: { - failCommands: ['commitTransaction'], - errorCode: 24 - } - }); + client = this.configuration.newClient(); }); afterEach(async function () { + sinon.restore(); await client?.close(); }); - for (let i = 0; i < 250; ++i) { - test.only( - 'works' + i, - { - requires: { - mongodb: '>=4.4', // failCommand - topology: '!single' // transactions can't run on standalone servers - } - }, - async function () { - const start = performance.now(); + test( + 'works', + { + requires: { + mongodb: '>=4.4', // failCommand + topology: '!single' // transactions can't run on standalone servers + } + }, + async function () { + const randomStub = sinon.stub(Math, 'random'); + + randomStub.returns(0); - await client.withSession(async s => { + await configureFailPoint(this.configuration, failCommand); + + const { duration: noBackoffTime } = await measureDuration(() => { + return client.withSession(async s => { await s.withTransaction(async s => { await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); }); }); + }); - const end = performance.now(); + randomStub.returns(1); - expect(failures).to.have.lengthOf(COMMIT_FAIL_TIMES); + await configureFailPoint(this.configuration, failCommand); - expect(end - start).to.be.greaterThan(1250); - } - ); - } + const { duration: fullBackoffDuration } = await measureDuration(() => { + return client.withSession(async s => { + await s.withTransaction(async s => { + await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); + }); + }); + }); + + expect(fullBackoffDuration).to.be.within( + noBackoffTime + 2200 - 1000, + noBackoffTime + 2200 + 1000 + ); + } + ); }); From 3dad6c255339079cccc943913792d2d38350f073 Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 18 Nov 2025 14:50:41 -0700 Subject: [PATCH 07/10] misc cleanup --- src/sessions.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index 9bb648b292..b149304028 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -788,22 +788,11 @@ export class ClientSession await this.commitTransaction(); committed = true; } catch (commitError) { + // If CSOT is enabled, we repeatedly retry until timeoutMS expires. + // If CSOT is not enabled, do we still have time remaining or have we timed out? const hasNotTimedOut = this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT; - /** - * will the provided backoffMS exceed the withTransaction's deadline? - */ - const willExceedTransactionDeadline = (backoffMS: number) => { - return ( - (this.timeoutContext?.csotEnabled() && - backoffMS > this.timeoutContext.remainingTimeMS) || - now() + backoffMS > startTime + MAX_TIMEOUT - ); - }; - - // If CSOT is enabled, we repeatedly retry until timeoutMS expires. - // If CSOT is not enabled, do we still have time remaining or have we timed out? if (hasNotTimedOut) { if ( !isMaxTimeMSExpiredError(commitError) && @@ -826,7 +815,12 @@ export class ClientSession const backoffMS = jitter * Math.min(BACKOFF_INITIAL_MS * 1.5 ** retry, BACKOFF_MAX_MS); - if (willExceedTransactionDeadline(backoffMS)) { + const willExceedTransactionDeadline = + (this.timeoutContext?.csotEnabled() && + backoffMS > this.timeoutContext.remainingTimeMS) || + now() + backoffMS > startTime + MAX_TIMEOUT; + + if (willExceedTransactionDeadline) { break; } @@ -841,6 +835,7 @@ export class ClientSession } } + // @ts-expect-error Result is always defined if we reach here, the for-loop above convinces TS it is not. return result; } finally { this.timeoutContext = null; From 46686b89ab021d96ab200ea26c834354ad1a6824 Mon Sep 17 00:00:00 2001 From: bailey Date: Tue, 25 Nov 2025 10:29:56 -0700 Subject: [PATCH 08/10] clarifying comment --- src/sessions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sessions.ts b/src/sessions.ts index b149304028..3ccc97f107 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -788,7 +788,9 @@ export class ClientSession await this.commitTransaction(); committed = true; } catch (commitError) { - // If CSOT is enabled, we repeatedly retry until timeoutMS expires. + // If CSOT is enabled, we repeatedly retry until timeoutMS expires. This is enforced by providing a + // timeoutContext to each async API, which know how to cancel themselves (i.e., the next retry will + // abort the withTransaction call). // If CSOT is not enabled, do we still have time remaining or have we timed out? const hasNotTimedOut = this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT; From 112c2fa5d9a25479dbef2c70e01b1a071c7b6542 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 26 Nov 2025 11:29:35 -0700 Subject: [PATCH 09/10] Pavel's comments --- src/sessions.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sessions.ts b/src/sessions.ts index 3ccc97f107..e816dadbd3 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -792,10 +792,10 @@ export class ClientSession // timeoutContext to each async API, which know how to cancel themselves (i.e., the next retry will // abort the withTransaction call). // If CSOT is not enabled, do we still have time remaining or have we timed out? - const hasNotTimedOut = - this.timeoutContext?.csotEnabled() || now() - startTime < MAX_TIMEOUT; + const hasTimedOut = + !this.timeoutContext?.csotEnabled() && now() - startTime >= MAX_TIMEOUT; - if (hasNotTimedOut) { + if (!hasTimedOut) { if ( !isMaxTimeMSExpiredError(commitError) && commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) @@ -813,9 +813,10 @@ export class ClientSession if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) { const BACKOFF_INITIAL_MS = 5; const BACKOFF_MAX_MS = 500; + const BACKOFF_GROWTH = 1.5; const jitter = Math.random(); const backoffMS = - jitter * Math.min(BACKOFF_INITIAL_MS * 1.5 ** retry, BACKOFF_MAX_MS); + jitter * Math.min(BACKOFF_INITIAL_MS * BACKOFF_GROWTH ** retry, BACKOFF_MAX_MS); const willExceedTransactionDeadline = (this.timeoutContext?.csotEnabled() && From 87ff29efba1f37506acb3929516ecea36c7ceef3 Mon Sep 17 00:00:00 2001 From: bailey Date: Wed, 3 Dec 2025 12:58:54 -0700 Subject: [PATCH 10/10] address Daria's prose test comments --- .../transactions-convenient-api.prose.test.ts | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts index 398cce6317..d1f0d6c56c 100644 --- a/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts +++ b/test/integration/transactions-convenient-api/transactions-convenient-api.prose.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { test } from 'mocha'; import * as sinon from 'sinon'; -import { type MongoClient } from '../../../src'; +import { type ClientSession, type Collection, type MongoClient } from '../../../src'; import { configureFailPoint, type FailCommandFailPoint, measureDuration } from '../../tools/utils'; const failCommand: FailCommandFailPoint = { @@ -12,15 +12,20 @@ const failCommand: FailCommandFailPoint = { }, data: { failCommands: ['commitTransaction'], - errorCode: 251 + errorCode: 251 // no such transaction } }; describe('Retry Backoff is Enforced', function () { + // 1. let client be a MongoClient let client: MongoClient; + // 2. let coll be a collection + let collection: Collection; + beforeEach(async function () { client = this.configuration.newClient(); + collection = client.db('foo').collection('bar'); }); afterEach(async function () { @@ -39,30 +44,40 @@ describe('Retry Backoff is Enforced', function () { async function () { const randomStub = sinon.stub(Math, 'random'); + // 3.i Configure the random number generator used for jitter to always return 0 randomStub.returns(0); + // 3.ii Configure a fail point that forces 13 retries await configureFailPoint(this.configuration, failCommand); + // 3.iii + const callback = async (s: ClientSession) => { + await collection.insertOne({}, { session: s }); + }; + + // 3.iv Let no_backoff_time be the duration of the withTransaction API call const { duration: noBackoffTime } = await measureDuration(() => { return client.withSession(async s => { - await s.withTransaction(async s => { - await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); - }); + await s.withTransaction(callback); }); }); + // 4.i Configure the random number generator used for jitter to always return 1. randomStub.returns(1); + // 4.ii Configure a fail point that forces 13 retries like in step 3.2. await configureFailPoint(this.configuration, failCommand); + // 4.iii Use the same callback defined in 3.3. + // 4.iv Let with_backoff_time be the duration of the withTransaction API call const { duration: fullBackoffDuration } = await measureDuration(() => { return client.withSession(async s => { - await s.withTransaction(async s => { - await client.db('foo').collection('bar').insertOne({ name: 'bailey' }, { session: s }); - }); + await s.withTransaction(callback); }); }); + // 5. Compare the two time between the two runs. + // The sum of 13 backoffs is roughly 2.2 seconds. There is a 1-second window to account for potential variance between the two runs. expect(fullBackoffDuration).to.be.within( noBackoffTime + 2200 - 1000, noBackoffTime + 2200 + 1000