diff --git a/src/cmap/connect.ts b/src/cmap/connect.ts index 62db2f98d4d..e9fecb26bf3 100644 --- a/src/cmap/connect.ts +++ b/src/cmap/connect.ts @@ -35,6 +35,11 @@ import { /** @public */ export type Stream = Socket | TLSSocket; +function applyBackpressureLabels(error: MongoError) { + error.addErrorLabel(MongoErrorLabel.SystemOverloadedError); + error.addErrorLabel(MongoErrorLabel.RetryableError); +} + export async function connect(options: ConnectionOptions): Promise { let connection: Connection | null = null; try { @@ -103,6 +108,8 @@ export async function performInitialHandshake( const authContext = new AuthContext(conn, credentials, options); conn.authContext = authContext; + // If we encounter an error preparing the handshake document, do NOT apply backpressure labels. Errors + // encountered building the handshake document are all client-side, and do not indicate an overloaded server. const handshakeDoc = await prepareHandshakeDocument(authContext); // @ts-expect-error: TODO(NODE-5141): The options need to be filtered properly, Connection options differ from Command options @@ -163,12 +170,15 @@ export async function performInitialHandshake( try { await provider.auth(authContext); } catch (error) { + // NOTE: If we encounter an error authenticating a connection, do NOT apply backpressure labels. + if (error instanceof MongoError) { error.addErrorLabel(MongoErrorLabel.HandshakeError); if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) { error.addErrorLabel(MongoErrorLabel.RetryableWriteError); } } + throw error; } } @@ -189,6 +199,9 @@ export async function performInitialHandshake( if (error instanceof MongoError) { error.addErrorLabel(MongoErrorLabel.HandshakeError); } + // If we encounter an error executing the initial handshake, apply backpressure labels. + applyBackpressureLabels(error); + throw error; } } @@ -424,6 +437,8 @@ export async function makeSocket(options: MakeConnectionOptions): Promise { return; } - const isStaleError = - error.connectionGeneration && error.connectionGeneration < this.pool.generation; - if (isStaleError) { + if (isStaleError(this, error)) { return; } @@ -401,33 +400,52 @@ export class Server extends TypedEventEmitter { error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError); const isNetworkTimeoutBeforeHandshakeError = error instanceof MongoNetworkError && error.beforeHandshake; - const isAuthHandshakeError = error.hasErrorLabel(MongoErrorLabel.HandshakeError); - if (isNetworkNonTimeoutError || isNetworkTimeoutBeforeHandshakeError || isAuthHandshakeError) { - // In load balanced mode we never mark the server as unknown and always - // clear for the specific service id. + const isAuthOrEstablishmentHandshakeError = error.hasErrorLabel(MongoErrorLabel.HandshakeError); + const isSystemOverloadError = error.hasErrorLabel(MongoErrorLabel.SystemOverloadedError); + + // TODO: considering parse errors as SDAM unrecoverable errors seem + // questionable. What if the parse error only comes from an application connection, + // indicating some bytes were lost in transmission? It seems overkill to completely + // kill the server. + // Parse errors from monitoring connections are already handled because the + // error would be wrapped in a ServerHeartbeatFailedEvent, which would mark the + // server unknown and clear the pool. Can we remove this? + if (isStateChangeError(error) || error instanceof MongoParseError) { + const shouldClearPool = isNodeShuttingDownError(error); + + // from the SDAM spec: The driver MUST synchronize clearing the pool with updating the topology. + // In load balanced mode: there is no monitoring, so there is no topology to update. We simply clear the pool. + // For other topologies: the `ResetPool` label instructs the topology to clear the server's pool in `updateServer()`. + if (!this.loadBalanced) { + if (shouldClearPool) { + error.addErrorLabel(MongoErrorLabel.ResetPool); + } + markServerUnknown(this, error); + process.nextTick(() => this.requestCheck()); + return; + } + + if (connection && shouldClearPool) { + this.pool.clear({ serviceId: connection.serviceId }); + } + } else if ( + isNetworkNonTimeoutError || + isNetworkTimeoutBeforeHandshakeError || + isAuthOrEstablishmentHandshakeError + ) { + // Do NOT clear the pool if we encounter a system overloaded error. + if (isSystemOverloadError) { + return; + } + // from the SDAM spec: The driver MUST synchronize clearing the pool with updating the topology. + // In load balanced mode: there is no monitoring, so there is no topology to update. We simply clear the pool. + // For other topologies: the `ResetPool` label instructs the topology to clear the server's pool in `updateServer()`. if (!this.loadBalanced) { error.addErrorLabel(MongoErrorLabel.ResetPool); markServerUnknown(this, error); } else if (connection) { this.pool.clear({ serviceId: connection.serviceId }); } - } else { - if (isSDAMUnrecoverableError(error)) { - if (shouldHandleStateChangeError(this, error)) { - const shouldClearPool = isNodeShuttingDownError(error); - if (this.loadBalanced && connection && shouldClearPool) { - this.pool.clear({ serviceId: connection.serviceId }); - } - - if (!this.loadBalanced) { - if (shouldClearPool) { - error.addErrorLabel(MongoErrorLabel.ResetPool); - } - markServerUnknown(this, error); - process.nextTick(() => this.requestCheck()); - } - } - } } } @@ -560,12 +578,6 @@ function connectionIsStale(pool: ConnectionPool, connection: Connection) { return connection.generation !== pool.generation; } -function shouldHandleStateChangeError(server: Server, err: MongoError) { - const etv = err.topologyVersion; - const stv = server.description.topologyVersion; - return compareTopologyVersion(stv, etv) < 0; -} - function inActiveTransaction(session: ClientSession | undefined, cmd: Document) { return session && session.inTransaction() && !isTransactionCommand(cmd); } @@ -575,3 +587,15 @@ function inActiveTransaction(session: ClientSession | undefined, cmd: Document) function isRetryableWritesEnabled(topology: Topology) { return topology.s.options.retryWrites !== false; } + +function isStaleError(server: Server, error: MongoError): boolean { + const currentGeneration = server.pool.generation; + const generation = error.connectionGeneration; + + if (generation && generation < currentGeneration) { + return true; + } + + const currentTopologyVersion = server.description.topologyVersion; + return compareTopologyVersion(currentTopologyVersion, error.topologyVersion) >= 0; +} diff --git a/test/integration/server-discovery-and-monitoring/server_discovery_and_monitoring.prose.test.ts b/test/integration/server-discovery-and-monitoring/server_discovery_and_monitoring.prose.test.ts index b0ed7adb07f..718e69d0fdf 100644 --- a/test/integration/server-discovery-and-monitoring/server_discovery_and_monitoring.prose.test.ts +++ b/test/integration/server-discovery-and-monitoring/server_discovery_and_monitoring.prose.test.ts @@ -1,13 +1,18 @@ import { expect } from 'chai'; import { once } from 'events'; -import { type MongoClient } from '../../../src'; +import { + type ConnectionCheckOutFailedEvent, + type ConnectionPoolClearedEvent, + type MongoClient +} from '../../../src'; import { CONNECTION_POOL_CLEARED, CONNECTION_POOL_READY, SERVER_HEARTBEAT_FAILED, SERVER_HEARTBEAT_SUCCEEDED } from '../../../src/constants'; +import { sleep } from '../../tools/utils'; describe('Server Discovery and Monitoring Prose Tests', function () { context('Monitors sleep at least minHeartbeatFrequencyMS between checks', function () { @@ -187,4 +192,74 @@ describe('Server Discovery and Monitoring Prose Tests', function () { } }); }); + + context('Connection Pool Backpressure', function () { + let client: MongoClient; + const checkoutFailedEvents: Array = []; + const poolClearedEvents: Array = []; + + beforeEach(async function () { + client = this.configuration.newClient({}, { maxConnecting: 100 }); + + client.on('connectionCheckOutFailed', e => checkoutFailedEvents.push(e)); + client.on('connectionPoolCleared', e => poolClearedEvents.push(e)); + + await client.connect(); + + const admin = client.db('admin').admin(); + await admin.command({ + setParameter: 1, + ingressConnectionEstablishmentRateLimiterEnabled: true + }); + await admin.command({ + setParameter: 1, + ingressConnectionEstablishmentRatePerSec: 20 + }); + await admin.command({ + setParameter: 1, + ingressConnectionEstablishmentBurstCapacitySecs: 1 + }); + await admin.command({ + setParameter: 1, + ingressConnectionEstablishmentMaxQueueDepth: 1 + }); + + await client.db('test').collection('test').insertOne({}); + }); + + afterEach(async function () { + // give the time to recover from the connection storm before cleaning up. + await sleep(1000); + + const admin = client.db('admin').admin(); + await admin.command({ + setParameter: 1, + ingressConnectionEstablishmentRateLimiterEnabled: false + }); + + await client.close(); + }); + + it( + 'does not clear the pool when connections are closed due to connection storms', + { + requires: { + mongodb: '>=7.0' // rate limiting added in 7.0 + } + }, + async function () { + await Promise.allSettled( + Array.from({ length: 100 }).map(() => + client + .db('test') + .collection('test') + .findOne({ $where: 'function() { sleep(2000); return true; }' }) + ) + ); + + expect(poolClearedEvents).to.be.empty; + expect(checkoutFailedEvents.length).to.be.greaterThan(10); + } + ); + }); }); diff --git a/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json b/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json index 509b2a2356c..fe7489f401a 100644 --- a/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json +++ b/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json @@ -17,7 +17,7 @@ "isMaster", "hello" ], - "closeConnection": true, + "errorCode": 91, "appName": "poolCreateMinSizeErrorTest" } }, diff --git a/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml b/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml index f43c4ee154f..42cf6e32a38 100644 --- a/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml +++ b/test/spec/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml @@ -11,7 +11,7 @@ failPoint: mode: { times: 50 } data: failCommands: ["isMaster","hello"] - closeConnection: true + errorCode: 91 appName: "poolCreateMinSizeErrorTest" poolOptions: minPoolSize: 1 diff --git a/test/spec/load-balancers/sdam-error-handling.json b/test/spec/load-balancers/sdam-error-handling.json index 4ab34b1fed4..2107afe5b3f 100644 --- a/test/spec/load-balancers/sdam-error-handling.json +++ b/test/spec/load-balancers/sdam-error-handling.json @@ -1,6 +1,6 @@ { "description": "state change errors are correctly handled", - "schemaVersion": "1.3", + "schemaVersion": "1.4", "runOnRequirements": [ { "topologies": [ @@ -263,7 +263,7 @@ "description": "errors during the initial connection hello are ignored", "runOnRequirements": [ { - "minServerVersion": "4.9" + "minServerVersion": "4.4.7" } ], "operations": [ @@ -282,7 +282,7 @@ "isMaster", "hello" ], - "closeConnection": true, + "errorCode": 11600, "appName": "lbSDAMErrorTestClient" } } @@ -297,7 +297,7 @@ } }, "expectError": { - "isClientError": true + "isError": true } } ], diff --git a/test/spec/load-balancers/sdam-error-handling.yml b/test/spec/load-balancers/sdam-error-handling.yml index e3d6d6a251b..c5a69339e34 100644 --- a/test/spec/load-balancers/sdam-error-handling.yml +++ b/test/spec/load-balancers/sdam-error-handling.yml @@ -1,6 +1,6 @@ description: state change errors are correctly handled -schemaVersion: '1.3' +schemaVersion: '1.4' runOnRequirements: - topologies: [ load-balanced ] @@ -141,9 +141,8 @@ tests: # to the same mongos on which the failpoint is set. - description: errors during the initial connection hello are ignored runOnRequirements: - # Server version 4.9+ is needed to set a fail point on the initial - # connection handshake with the appName filter due to SERVER-49336. - - minServerVersion: '4.9' + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: '4.4.7' operations: - name: failPoint object: testRunner @@ -154,14 +153,14 @@ tests: mode: { times: 1 } data: failCommands: [isMaster, hello] - closeConnection: true + errorCode: 11600 appName: *singleClientAppName - name: insertOne object: *singleColl arguments: document: { x: 1 } expectError: - isClientError: true + isError: true expectEvents: - client: *singleClient eventType: cmap diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.json b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.json new file mode 100644 index 00000000000..f41b76459cb --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.json @@ -0,0 +1,140 @@ +{ + "description": "backpressure-network-error-fail", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-error-fail", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network connection errors during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatSucceededEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "serverMonitoringMode": "poll", + "appname": "backpressureNetworkErrorFailTest" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-error-fail" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatSucceededEvent": {} + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "appName": "backpressureNetworkErrorFailTest", + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.yml b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.yml new file mode 100644 index 00000000000..54e30302115 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-error-fail.yml @@ -0,0 +1,80 @@ +description: backpressure-network-error-fail +schemaVersion: "1.17" +runOnRequirements: + - minServerVersion: "4.4" + serverless: forbid + topologies: + - single + - replicaset + - sharded +createEntities: + - client: + id: setupClient + useMultipleMongoses: false +initialData: + - collectionName: backpressure-network-error-fail + databaseName: sdam-tests + documents: + - _id: 1 + - _id: 2 +tests: + - description: apply backpressure on network connection errors during connection establishment + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + useMultipleMongoses: false + observeEvents: + - serverHeartbeatSucceededEvent + - poolClearedEvent + uriOptions: + retryWrites: false + heartbeatFrequencyMS: 1000000 + serverMonitoringMode: poll + appname: backpressureNetworkErrorFailTest + - database: + id: database + client: client + databaseName: sdam-tests + - collection: + id: collection + database: database + collectionName: backpressure-network-error-fail + - name: waitForEvent + object: testRunner + arguments: + client: client + event: + serverHeartbeatSucceededEvent: {} + count: 1 + - name: failPoint + object: testRunner + arguments: + client: setupClient + failPoint: + configureFailPoint: failCommand + mode: alwaysOn + data: + failCommands: + - isMaster + - hello + appName: backpressureNetworkErrorFailTest + closeConnection: true + - name: insertMany + object: collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + errorLabelsContain: + - SystemOverloadedError + - RetryableError + expectEvents: + - client: client + eventType: cmap + events: [] diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.json b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.json new file mode 100644 index 00000000000..a97c7a329ff --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.json @@ -0,0 +1,143 @@ +{ + "description": "backpressure-network-timeout-error", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-timeout-error", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network timeout error during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverDescriptionChangedEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "appname": "backpressureNetworkTimeoutErrorTest", + "serverMonitoringMode": "poll", + "connectTimeoutMS": 250, + "socketTimeoutMS": 250 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-timeout-error" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": {} + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "blockConnection": true, + "blockTimeMS": 500, + "appName": "backpressureNetworkTimeoutErrorTest" + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.yml b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.yml new file mode 100644 index 00000000000..6a61eba3add --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-network-timeout-fail.yml @@ -0,0 +1,83 @@ +description: backpressure-network-timeout-error +schemaVersion: "1.17" +runOnRequirements: + - minServerVersion: "4.4" + serverless: forbid + topologies: + - single + - replicaset + - sharded +createEntities: + - client: + id: setupClient + useMultipleMongoses: false +initialData: + - collectionName: backpressure-network-timeout-error + databaseName: sdam-tests + documents: + - _id: 1 + - _id: 2 +tests: + - description: apply backpressure on network timeout error during connection establishment + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + useMultipleMongoses: false + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + heartbeatFrequencyMS: 1000000 + appname: backpressureNetworkTimeoutErrorTest + serverMonitoringMode: poll + connectTimeoutMS: 250 + socketTimeoutMS: 250 + - database: + id: database + client: client + databaseName: sdam-tests + - collection: + id: collection + database: database + collectionName: backpressure-network-timeout-error + - name: waitForEvent + object: testRunner + arguments: + client: client + event: + serverDescriptionChangedEvent: {} + count: 1 + - name: failPoint + object: testRunner + arguments: + client: setupClient + failPoint: + configureFailPoint: failCommand + mode: alwaysOn + data: + failCommands: + - isMaster + - hello + blockConnection: true + blockTimeMS: 500 + appName: backpressureNetworkTimeoutErrorTest + - name: insertMany + object: collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + errorLabelsContain: + - SystemOverloadedError + - RetryableError + expectEvents: + - client: client + eventType: cmap + events: [] diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json b/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json new file mode 100644 index 00000000000..35a49c1323d --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json @@ -0,0 +1,106 @@ +{ + "description": "backpressure-server-description-unchanged-on-min-pool-size-population-error", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "the server description is not changed on handshake error during minPoolSize population", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "serverDescriptionChangedEvent", + "connectionClosedEvent" + ], + "uriOptions": { + "appname": "authErrorTest", + "minPoolSize": 5, + "maxConnecting": 1, + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 1000000 + } + } + } + ] + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "skip": 1 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "authErrorTest", + "closeConnection": true + } + } + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "events": [ + { + "serverDescriptionChangedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.yml b/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.yml new file mode 100644 index 00000000000..dd5029097d1 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.yml @@ -0,0 +1,62 @@ +description: backpressure-server-description-unchanged-on-min-pool-size-population-error +schemaVersion: "1.17" +runOnRequirements: + - minServerVersion: "4.4" + serverless: forbid + topologies: + - single +createEntities: + - client: + id: setupClient + useMultipleMongoses: false +tests: + - description: the server description is not changed on handshake error during minPoolSize population + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + observeEvents: + - serverDescriptionChangedEvent + - connectionClosedEvent + uriOptions: + appname: authErrorTest + minPoolSize: 5 + maxConnecting: 1 + serverMonitoringMode: poll + heartbeatFrequencyMS: 1000000 + - name: failPoint + object: testRunner + arguments: + client: setupClient + failPoint: + configureFailPoint: failCommand + mode: + skip: 1 + data: + failCommands: + - hello + - isMaster + appName: authErrorTest + closeConnection: true + - name: waitForEvent + object: testRunner + arguments: + client: client + event: + serverDescriptionChangedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: client + event: + connectionClosedEvent: {} + count: 1 + expectEvents: + - client: client + eventType: sdam + events: + - serverDescriptionChangedEvent: {} diff --git a/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json b/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json index 126ee545333..7e6c7c8df4d 100644 --- a/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json +++ b/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json @@ -143,154 +143,6 @@ ] } ] - }, - { - "description": "Pool is cleared before connection is closed (handshake error)", - "runOnRequirements": [ - { - "topologies": [ - "single" - ] - } - ], - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "connectionCheckOutStartedEvent", - "poolClearedEvent", - "connectionClosedEvent", - "topologyDescriptionChangedEvent" - ], - "uriOptions": { - "retryWrites": false, - "appname": "authErrorTest", - "minPoolSize": 0, - "serverMonitoringMode": "poll", - "heartbeatFrequencyMS": 1000000 - } - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "foo" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "bar" - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "topologyDescriptionChangedEvent": { - "previousDescription": { - "type": "Unknown" - }, - "newDescription": { - "type": "Single" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "hello", - "isMaster" - ], - "appName": "authErrorTest", - "closeConnection": true - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - "expectError": { - "isError": true - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "poolClearedEvent": {} - }, - "count": 1 - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "connectionClosedEvent": {} - }, - "count": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCheckOutStartedEvent": {} - }, - { - "poolClearedEvent": {} - }, - { - "connectionClosedEvent": {} - } - ] - } - ] } ] } diff --git a/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml b/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml index 8df74b6a6f5..5f7c48b5210 100644 --- a/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml +++ b/test/spec/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml @@ -84,93 +84,3 @@ tests: - connectionCheckOutStartedEvent: {} - poolClearedEvent: {} - connectionClosedEvent: {} - - - description: Pool is cleared before connection is closed (handshake error) - runOnRequirements: - - topologies: [ single ] - operations: - - name: createEntities - object: testRunner - arguments: - entities: - - client: - id: &client client - useMultipleMongoses: false - observeEvents: - - connectionCheckOutStartedEvent - - poolClearedEvent - - connectionClosedEvent - - topologyDescriptionChangedEvent - uriOptions: - retryWrites: false - appname: authErrorTest - minPoolSize: 0 - # ensure that once we've connected to the server, the failCommand won't - # be triggered by monitors and will only be triggered by handshakes - serverMonitoringMode: poll - heartbeatFrequencyMS: 1000000 - - database: - id: &database database - client: *client - databaseName: foo - - collection: - id: &collection collection - database: *database - collectionName: bar - - name: waitForEvent - object: testRunner - arguments: - client: *client - event: - topologyDescriptionChangedEvent: - previousDescription: - type: "Unknown" - newDescription: - type: "Single" - count: 1 - - - name: failPoint - object: testRunner - arguments: - client: *setupClient - failPoint: - configureFailPoint: failCommand - mode: - times: 1 - data: - failCommands: - - hello - - isMaster - appName: authErrorTest - closeConnection: true - - - name: insertMany - object: *collection - arguments: - documents: - - _id: 3 - - _id: 4 - expectError: - isError: true - - name: waitForEvent - object: testRunner - arguments: - client: *client - event: - poolClearedEvent: {} - count: 1 - - name: waitForEvent - object: testRunner - arguments: - client: *client - event: - connectionClosedEvent: {} - count: 1 - expectEvents: - - client: *client - eventType: cmap - events: - - connectionCheckOutStartedEvent: {} - - poolClearedEvent: {} - - connectionClosedEvent: {} - diff --git a/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json b/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json index 11c6be5bc16..e36dd7aa61a 100644 --- a/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json +++ b/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json @@ -109,122 +109,6 @@ ] } ] - }, - { - "description": "Pool is cleared on handshake error during minPoolSize population", - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "observeEvents": [ - "topologyDescriptionChangedEvent", - "connectionCreatedEvent", - "poolClearedEvent", - "connectionClosedEvent", - "connectionReadyEvent" - ], - "uriOptions": { - "appname": "authErrorTest", - "minPoolSize": 5, - "maxConnecting": 1, - "serverMonitoringMode": "poll", - "heartbeatFrequencyMS": 1000000 - } - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "topologyDescriptionChangedEvent": { - "previousDescription": { - "type": "Unknown" - }, - "newDescription": { - "type": "Single" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "hello", - "isMaster" - ], - "appName": "authErrorTest", - "closeConnection": true - } - } - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "poolClearedEvent": {} - }, - "count": 1 - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "connectionClosedEvent": {} - }, - "count": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [ - { - "connectionCreatedEvent": {} - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCreatedEvent": {} - }, - { - "poolClearedEvent": {} - }, - { - "connectionClosedEvent": {} - } - ] - } - ] } ] } diff --git a/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml b/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml index 7e7ef0c590b..8b58f672d32 100644 --- a/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml +++ b/test/spec/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml @@ -68,7 +68,7 @@ tests: - poolClearedEvent: {} - connectionClosedEvent: {} - - description: Pool is cleared on handshake error during minPoolSize population + - description: Pool is not cleared on handshake error during minPoolSize population operations: - name: createEntities object: testRunner @@ -118,13 +118,6 @@ tests: appName: authErrorTest closeConnection: true - - name: waitForEvent - object: testRunner - arguments: - client: *client - event: - poolClearedEvent: {} - count: 1 - name: waitForEvent object: testRunner arguments: @@ -139,6 +132,4 @@ tests: - connectionCreatedEvent: {} - connectionReadyEvent: {} - connectionCreatedEvent: {} - - poolClearedEvent: {} - connectionClosedEvent: {} - diff --git a/test/unit/error.test.ts b/test/unit/error.test.ts index 32e90a07857..34428b0666f 100644 --- a/test/unit/error.test.ts +++ b/test/unit/error.test.ts @@ -10,7 +10,7 @@ import * as importsFromErrorSrc from '../../src/error'; import { isResumableError, isRetryableReadError, - isSDAMUnrecoverableError, + isStateChangeError, LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE, LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE, MONGODB_ERROR_CODES, @@ -26,7 +26,6 @@ import { MongoNetworkError, MongoNetworkTimeoutError, MongoOperationTimeoutError, - MongoParseError, MongoRuntimeError, MongoServerError, MongoSystemError, @@ -211,26 +210,13 @@ describe('MongoErrors', () => { }); }); - describe('#isSDAMUnrecoverableError', function () { - context('when the error is a MongoParseError', function () { - it('returns true', function () { - const error = new MongoParseError(''); - expect(isSDAMUnrecoverableError(error)).to.be.true; - }); - }); - - context('when the error is null', function () { - it('returns true', function () { - expect(isSDAMUnrecoverableError(null)).to.be.true; - }); - }); - + describe('#isStateChangeError', function () { context('when the error has a "node is recovering" error code', function () { it('returns true', function () { const error = new MongoError(''); // Code for NotPrimaryOrSecondary error.code = 13436; - expect(isSDAMUnrecoverableError(error)).to.be.true; + expect(isStateChangeError(error)).to.be.true; }); }); @@ -239,7 +225,7 @@ describe('MongoErrors', () => { const error = new MongoError(''); // Code for NotWritablePrimary error.code = 10107; - expect(isSDAMUnrecoverableError(error)).to.be.true; + expect(isStateChangeError(error)).to.be.true; }); }); @@ -250,7 +236,7 @@ describe('MongoErrors', () => { // If the response includes an error code, it MUST be solely used to determine if error is a "node is recovering" or "not writable primary" error. const error = new MongoError(NODE_IS_RECOVERING_ERROR_MESSAGE.source); error.code = 555; - expect(isSDAMUnrecoverableError(error)).to.be.false; + expect(isStateChangeError(error)).to.be.false; }); } ); @@ -262,7 +248,7 @@ describe('MongoErrors', () => { const error = new MongoError( `this is ${LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.source}.` ); - expect(isSDAMUnrecoverableError(error)).to.be.true; + expect(isStateChangeError(error)).to.be.true; }); } ); @@ -272,7 +258,7 @@ describe('MongoErrors', () => { function () { it('returns true', function () { const error = new MongoError(`the ${NODE_IS_RECOVERING_ERROR_MESSAGE} from an error`); - expect(isSDAMUnrecoverableError(error)).to.be.true; + expect(isStateChangeError(error)).to.be.true; }); } ); @@ -284,7 +270,7 @@ describe('MongoErrors', () => { const error = new MongoError( `this is ${LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE}, so we have a problem ` ); - expect(isSDAMUnrecoverableError(error)).to.be.true; + expect(isStateChangeError(error)).to.be.true; }); } );