Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/operations/execute_operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../error';
import type { MongoClient } from '../mongo_client';
import { ReadPreference } from '../read_preference';
import { TopologyType } from '../sdam/common';
import {
DeprioritizedServers,
sameServerSelector,
Expand Down Expand Up @@ -304,7 +305,13 @@ async function tryOperation<T extends AbstractOperation, TResult = ResultTypeFro
) {
throw previousOperationError;
}
deprioritizedServers.add(server.description);
if (
topology.description.type === TopologyType.Sharded ||
operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError)
) {
deprioritizedServers.add(server.description);
}

previousOperationError = operationError;

// Reset timeouts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect } from 'chai';

import { type Collection, type MongoClient } from '../../mongodb';
import {
type Collection,
type CommandFailedEvent,
type CommandSucceededEvent,
type MongoClient
} from '../../mongodb';
import { filterForCommands } from '../shared';

describe('Retryable Reads Spec Prose', () => {
let client: MongoClient, failPointName;
Expand Down Expand Up @@ -136,4 +142,103 @@ describe('Retryable Reads Spec Prose', () => {
}
});
});

describe('Retrying Reads in a Replica Set', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you copy paste the actual prose test steps on each line as a comment, including any numbering of steps or cases (and make sure the describe and it blocks mirror the prose text as much as possible) - we do this as a matter of best practice when implementing prose tests so that they're easier to audit (and it helps in the rare cases when prose tests in the spec are later amended). You can follow the example of the case above this one.

// These tests verify that server deprioritization on replica sets only occurs
// for SystemOverloadedError errors.

const TEST_METADATA: MongoDBMetadataUI = {
requires: { mongodb: '>=4.4', topology: 'replicaset' }
};

describe('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => {
let client: MongoClient;
const commandFailedEvents: CommandFailedEvent[] = [];
const commandSucceededEvents: CommandSucceededEvent[] = [];

beforeEach(async function () {
client = this.configuration.newClient({
retryReads: true,
readPreference: 'primaryPreferred',
monitorCommands: true
});

client.on('commandFailed', filterForCommands('find', commandFailedEvents));
client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents));

await client.connect();

await client.db('admin').command({
configureFailPoint: 'failCommand',
mode: { times: 1 },
data: {
failCommands: ['find'],
errorCode: 6,
errorLabels: ['RetryableError', 'SystemOverloadedError']
}
});

commandFailedEvents.length = 0;
commandSucceededEvents.length = 0;
});

afterEach(async function () {
await client?.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' });
await client?.close();
});

it('retries on a different server when SystemOverloadedError', TEST_METADATA, async () => {
await client.db('test').collection('test').find().toArray();

expect(commandFailedEvents).to.have.lengthOf(1);
expect(commandSucceededEvents).to.have.lengthOf(1);
expect(commandFailedEvents[0].address).to.not.equal(commandSucceededEvents[0].address);
});
});

describe('Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Server', () => {
let client: MongoClient;
const commandFailedEvents: CommandFailedEvent[] = [];
const commandSucceededEvents: CommandSucceededEvent[] = [];

beforeEach(async function () {
client = this.configuration.newClient({
retryReads: true,
readPreference: 'primaryPreferred',
monitorCommands: true
});

client.on('commandFailed', filterForCommands('find', commandFailedEvents));
client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents));

await client.connect();

await client.db('admin').command({
configureFailPoint: 'failCommand',
mode: { times: 1 },
data: {
failCommands: ['find'],
errorCode: 6,
errorLabels: ['RetryableError']
}
});

commandFailedEvents.length = 0;
commandSucceededEvents.length = 0;
});

afterEach(async function () {
await client?.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' });
await client?.close();
});

it('retries on the same server when no SystemOverloadedError', TEST_METADATA, async () => {
await client.db('test').collection('test').find().toArray();

expect(commandFailedEvents).to.have.lengthOf(1);
expect(commandSucceededEvents).to.have.lengthOf(1);
expect(commandFailedEvents[0].address).to.equal(commandSucceededEvents[0].address);
});
});
});
});