Skip to content

Commit db39675

Browse files
add prose tests and latest versions of tests
1 parent 5ffe949 commit db39675

10 files changed

+3461
-228
lines changed

src/operations/execute_operation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ async function executeOperationWithRetries<
280280
}
281281

282282
// if we have exhausted overload retry attempts, throw.
283-
if (systemOverloadRetryAttempt >= maxSystemOverloadRetryAttempts) {
283+
if (systemOverloadRetryAttempt > maxSystemOverloadRetryAttempts) {
284284
throw previousOperationError;
285285
}
286286

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect } from 'chai';
2+
import * as sinon from 'sinon';
3+
4+
import { type Collection, type MongoClient, MongoServerError } from '../../../src';
5+
import { clearFailPoint, configureFailPoint, measureDuration } from '../../tools/utils';
6+
7+
describe('Client Backpressure (Prose)', function () {
8+
let client: MongoClient;
9+
let collection: Collection;
10+
11+
beforeEach(async function () {
12+
client = this.configuration.newClient();
13+
await client.connect();
14+
15+
collection = client.db('foo').collection('bar');
16+
});
17+
18+
afterEach(async function () {
19+
await client.close();
20+
await clearFailPoint(this.configuration);
21+
});
22+
23+
it(
24+
'Test 1: Operation Retry Uses Exponential Backoff',
25+
{
26+
requires: {
27+
mongodb: '4.4'
28+
}
29+
},
30+
async function () {
31+
await configureFailPoint(this.configuration, {
32+
configureFailPoint: 'failCommand',
33+
mode: 'alwaysOn',
34+
data: {
35+
failCommands: ['insert'],
36+
errorCode: 2,
37+
errorLabels: ['SystemOverloadedError', 'RetryableError']
38+
}
39+
});
40+
41+
const stub = sinon.stub(Math, 'random');
42+
43+
stub.returns(0);
44+
45+
const { duration: durationNoBackoff } = await measureDuration(async () => {
46+
const error = await collection.insertOne({ a: 1 }).catch(e => e);
47+
expect(error).to.be.instanceof(MongoServerError);
48+
});
49+
50+
stub.returns(1);
51+
52+
const { duration: durationBackoff } = await measureDuration(async () => {
53+
const error = await collection.insertOne({ a: 1 }).catch(e => e);
54+
expect(error).to.be.instanceof(MongoServerError);
55+
});
56+
57+
expect(durationBackoff - durationNoBackoff).to.be.within(3100 - 1000, 3100 + 1000);
58+
}
59+
);
60+
});

test/integration/client-backpressure/client-backpressure.spec.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner';
33
import { type Test } from '../../tools/unified-spec-runner/schema';
44

55
const skippedTests = {
6-
'collection.dropIndexes retries at most maxAttempts times (maxAttempts=5)':
6+
'collection.dropIndexes retries at most maxAttempts=5 times':
77
'TODO(NODE-6517): dropIndexes squashes all errors other than ns not found'
88
};
99

test/spec/client-backpressure/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,51 @@ retryable reads. These tests utilize the [Unified Test Format](../../unified-tes
1010
Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to
1111
be manually implemented by each driver.
1212

13+
### Prose Tests
14+
15+
#### Test 1: Operation Retry Uses Exponential Backoff
16+
17+
Drivers should test that retries do not occur immediately when a SystemOverloadedError is encountered.
18+
19+
1. let `client` be a `MongoClient`
20+
2. let `collection` be a collection
21+
3. Now, run transactions without backoff:
22+
1. Configure the random number generator used for jitter to always return `0` -- this effectively disables backoff.
23+
24+
2. Configure the following failPoint:
25+
26+
```javascript
27+
{
28+
configureFailPoint: 'failCommand',
29+
mode: 'alwaysOn',
30+
data: {
31+
failCommands: ['insert'],
32+
errorCode: 2,
33+
errorLabels: ['SystemOverloadedError', 'RetryableError']
34+
}
35+
}
36+
```
37+
38+
3. Execute the following command. Expect that the command errors. Measure the duration of the command execution.
39+
40+
```javascript
41+
const start = performance.now();
42+
expect(
43+
await coll.insertOne({ a: 1 }).catch(e => e)
44+
).to.be.an.instanceof(MongoServerError);
45+
const end = performance.now();
46+
```
47+
48+
4. Configure the random number generator used for jitter to always return `1`.
49+
5. Execute step 3 again.
50+
6. Compare the two time between the two runs.
51+
```python
52+
assertTrue(absolute_value(with_backoff_time - (no_backoff_time + 3.1 seconds)) < 1)
53+
```
54+
The sum of 5 backoffs is 3.1 seconds. There is a 1-second window to account for potential variance between
55+
the two runs.
56+
57+
1358
## Changelog
1459

1560
- 2025-XX-XX: Initial version.

test/spec/client-backpressure/backpressure-retry-loop.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,9 @@
381381
},
382382
{
383383
"description": "client.clientBulkWrite retries using operation loop",
384+
"runOnRequirements": {
385+
"minServerVersion": "8.0"
386+
},
384387
"operations": [
385388
{
386389
"object": "utilCollection",

test/spec/client-backpressure/backpressure-retry-loop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ tests:
223223

224224
-
225225
description: 'client.clientBulkWrite retries using operation loop'
226+
runOnRequirements:
227+
minServerVersion: '8.0'
226228
operations:
227229
-
228230
object: *utilCollection

test/spec/client-backpressure/backpressure-retry-loop.yml.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ tests:
5656
{% for operation in operations %}
5757
-
5858
description: '{{operation.object}}.{{operation.operation_name}} retries using operation loop'
59+
{%- if ((operation.operation_name == 'clientBulkWrite')) %}
60+
runOnRequirements:
61+
minServerVersion: '8.0'
62+
{%- endif %}
5963
operations:
6064
-
6165
object: *utilCollection

0 commit comments

Comments
 (0)