Skip to content

Commit 08c9fb4

Browse files
fix(NODE-4863): do not use RetryableWriteError for non-server errors (#3914)
Co-authored-by: Durran Jordan <durran@gmail.com>
1 parent 54adc9f commit 08c9fb4

File tree

4 files changed

+59
-5
lines changed

4 files changed

+59
-5
lines changed

src/cmap/errors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class PoolClearedError extends MongoNetworkError {
5656
super(errorMessage, pool.serverError ? { cause: pool.serverError } : undefined);
5757
this.address = pool.address;
5858

59-
this.addErrorLabel(MongoErrorLabel.RetryableWriteError);
59+
this.addErrorLabel(MongoErrorLabel.PoolRequstedRetry);
6060
}
6161

6262
override get name(): string {

src/error.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export const MongoErrorLabel = Object.freeze({
9292
ResumableChangeStreamError: 'ResumableChangeStreamError',
9393
HandshakeError: 'HandshakeError',
9494
ResetPool: 'ResetPool',
95+
PoolRequstedRetry: 'PoolRequstedRetry',
9596
InterruptInUseConnections: 'InterruptInUseConnections',
9697
NoWritesPerformed: 'NoWritesPerformed'
9798
} as const);
@@ -1162,7 +1163,7 @@ export function needsRetryableWriteLabel(error: Error, maxWireVersion: number):
11621163

11631164
if (error instanceof MongoError) {
11641165
if (
1165-
(maxWireVersion >= 9 || error.hasErrorLabel(MongoErrorLabel.RetryableWriteError)) &&
1166+
(maxWireVersion >= 9 || isRetryableWriteError(error)) &&
11661167
!error.hasErrorLabel(MongoErrorLabel.HandshakeError)
11671168
) {
11681169
// If we already have the error label no need to add it again. 4.4+ servers add the label.
@@ -1194,7 +1195,10 @@ export function needsRetryableWriteLabel(error: Error, maxWireVersion: number):
11941195
}
11951196

11961197
export function isRetryableWriteError(error: MongoError): boolean {
1197-
return error.hasErrorLabel(MongoErrorLabel.RetryableWriteError);
1198+
return (
1199+
error.hasErrorLabel(MongoErrorLabel.RetryableWriteError) ||
1200+
error.hasErrorLabel(MongoErrorLabel.PoolRequstedRetry)
1201+
);
11981202
}
11991203

12001204
/** Determines whether an error is something the driver should attempt to retry */

src/sessions.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { PINNED, UNPINNED } from './constants';
88
import type { AbstractCursor } from './cursor/abstract_cursor';
99
import {
1010
type AnyError,
11+
isRetryableWriteError,
1112
MongoAPIError,
1213
MongoCompatibilityError,
1314
MONGODB_ERROR_CODES,
@@ -731,7 +732,7 @@ function endTransaction(
731732
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
732733
if (error instanceof MongoError) {
733734
if (
734-
error.hasErrorLabel(MongoErrorLabel.RetryableWriteError) ||
735+
isRetryableWriteError(error) ||
735736
error instanceof MongoWriteConcernError ||
736737
isMaxTimeMSExpiredError(error)
737738
) {
@@ -767,7 +768,7 @@ function endTransaction(
767768
session.unpin();
768769
}
769770

770-
if (error instanceof MongoError && error.hasErrorLabel(MongoErrorLabel.RetryableWriteError)) {
771+
if (error instanceof MongoError && isRetryableWriteError(error)) {
771772
// SPEC-1185: apply majority write concern when retrying commitTransaction
772773
if (command.commitTransaction) {
773774
// per txns spec, must unpin session in this case
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect } from 'chai';
2+
import * as sinon from 'sinon';
3+
4+
import {
5+
type Collection,
6+
type MongoClient,
7+
MongoWriteConcernError,
8+
PoolClearedError,
9+
Server
10+
} from '../../mongodb';
11+
12+
describe('Non Server Retryable Writes', function () {
13+
let client: MongoClient;
14+
let collection: Collection<{ _id: 1 }>;
15+
16+
beforeEach(async function () {
17+
client = this.configuration.newClient({ monitorCommands: true, retryWrites: true });
18+
await client
19+
.db()
20+
.collection('retryReturnsOriginal')
21+
.drop()
22+
.catch(() => null);
23+
collection = client.db().collection('retryReturnsOriginal');
24+
});
25+
26+
afterEach(async function () {
27+
sinon.restore();
28+
await client.close();
29+
});
30+
31+
it(
32+
'returns the original error with a PoolRequstedRetry label after encountering a WriteConcernError',
33+
{ requires: { topology: 'replicaset', mongodb: '>=4.2.9' } },
34+
async () => {
35+
const serverCommandStub = sinon.stub(Server.prototype, 'command');
36+
serverCommandStub.onCall(0).yieldsRight(new PoolClearedError('error'));
37+
serverCommandStub
38+
.onCall(1)
39+
.yieldsRight(
40+
new MongoWriteConcernError({ errorLabels: ['NoWritesPerformed'], errorCode: 10107 }, {})
41+
);
42+
43+
const insertResult = await collection.insertOne({ _id: 1 }).catch(error => error);
44+
sinon.restore();
45+
46+
expect(insertResult.errorLabels).to.be.deep.equal(['PoolRequstedRetry']);
47+
}
48+
);
49+
});

0 commit comments

Comments
 (0)