From 728ab9ef1be3d5ba406916a569a158eacb70b7cc Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 9 Aug 2024 14:31:43 -0400 Subject: [PATCH] chore: add looping for writeErrors --- src/cmap/connection.ts | 5 ++- src/cmap/wire_protocol/responses.ts | 37 +++++++++++++------ .../node_csot.test.ts | 14 ++++--- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 68608d45b0..d70c86be70 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -621,7 +621,10 @@ export class Connection extends TypedEventEmitter { } } else { if ( - document?.writeErrors?.[0]?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired || + (Array.isArray(document?.writeErrors) && + document.writeErrors.some( + error => error?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired + )) || document?.writeConcernError?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired ) { throw new MongoOperationTimeoutError('Server reported a timeout error', { diff --git a/src/cmap/wire_protocol/responses.ts b/src/cmap/wire_protocol/responses.ts index a7d520eb38..347e84f9c1 100644 --- a/src/cmap/wire_protocol/responses.ts +++ b/src/cmap/wire_protocol/responses.ts @@ -111,18 +111,31 @@ export class MongoDBResponse extends OnDemandDocument { * - ok is 1 and the writeConcern object contains a code === 50 */ get isMaxTimeExpiredError() { - return ( - // {ok: 0, code: 50 ... } - (this.ok === 0 && this.code === MONGODB_ERROR_CODES.MaxTimeMSExpired) || - // {ok: 1, writeErrors: [{code: 50 ... }]} - (this.ok === 1 && - this.get('writeErrors', BSONType.array)?.get(0, BSONType.object)?.getNumber('code') === - MONGODB_ERROR_CODES.MaxTimeMSExpired) || - // {ok: 1, writeConcernError: {code: 50 ... }} - (this.ok === 1 && - this.get('writeConcernError', BSONType.object)?.getNumber('code') === - MONGODB_ERROR_CODES.MaxTimeMSExpired) - ); + // {ok: 0, code: 50 ... } + const isTopLevel = this.ok === 0 && this.code === MONGODB_ERROR_CODES.MaxTimeMSExpired; + if (isTopLevel) return true; + + if (this.ok === 0) return false; + + // {ok: 1, writeConcernError: {code: 50 ... }} + const isWriteConcern = + this.get('writeConcernError', BSONType.object)?.getNumber('code') === + MONGODB_ERROR_CODES.MaxTimeMSExpired; + if (isWriteConcern) return true; + + const writeErrors = this.get('writeErrors', BSONType.array); + if (writeErrors?.size()) { + for (let i = 0; i < writeErrors.size(); i++) { + const isWriteError = + writeErrors.get(i, BSONType.object)?.getNumber('code') === + MONGODB_ERROR_CODES.MaxTimeMSExpired; + + // {ok: 1, writeErrors: [{code: 50 ... }]} + if (isWriteError) return true; + } + } + + return false; } /** diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index 1fccd505de..53975bbc12 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -1,11 +1,11 @@ /* Anything javascript specific relating to timeouts */ import { expect } from 'chai'; -import * as semver from 'semver'; import * as sinon from 'sinon'; import { BSON, type ClientSession, + Code, type Collection, Connection, type Db, @@ -232,14 +232,16 @@ describe('CSOT driver tests', () => { }); describe('when a maxTimeExpired error is returned inside a writeErrors array', () => { - // Okay so allegedly this can never happen. - // But the spec says it can, so let's be defensive and support it. - // {ok: 1, writeErrors: [{code: 50, codeName: "MaxTimeMSExpired", errmsg: "operation time limit exceeded"}]} + // The server should always return one maxTimeExpiredError at the front of the writeErrors array + // But for the sake of defensive programming we will find any maxTime error in the array. beforeEach(async () => { const writeErrorsReply = BSON.serialize({ ok: 1, writeErrors: [ + { code: 2, codeName: 'MaxTimeMSExpired', errmsg: 'operation time limit exceeded' }, + { code: 3, codeName: 'MaxTimeMSExpired', errmsg: 'operation time limit exceeded' }, + { code: 4, codeName: 'MaxTimeMSExpired', errmsg: 'operation time limit exceeded' }, { code: 50, codeName: 'MaxTimeMSExpired', errmsg: 'operation time limit exceeded' } ] }); @@ -268,10 +270,10 @@ describe('CSOT driver tests', () => { .catch(error => error); expect(error).to.be.instanceOf(MongoOperationTimeoutError); expect(error.cause).to.be.instanceOf(MongoServerError); - expect(error.cause).to.have.nested.property('writeErrors[0].code', 50); + expect(error.cause).to.have.nested.property('writeErrors[3].code', 50); expect(commandsSucceeded).to.have.lengthOf(1); - expect(commandsSucceeded).to.have.nested.property('[0].reply.writeErrors[0].code', 50); + expect(commandsSucceeded).to.have.nested.property('[0].reply.writeErrors[3].code', 50); }); });