diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index bbe65a2510..c1cbf68b2d 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -247,9 +247,9 @@ export class Connection extends TypedEventEmitter { this.lastUseTime = now(); this.messageStream = this.socket - .on('error', this.onError.bind(this)) + .on('error', this.onSocketError.bind(this)) .pipe(new SizedMessageTransform({ connection: this })) - .on('error', this.onError.bind(this)); + .on('error', this.onTransformError.bind(this)); this.socket.on('close', this.onClose.bind(this)); this.socket.on('timeout', this.onTimeout.bind(this)); @@ -304,6 +304,14 @@ export class Connection extends TypedEventEmitter { this.lastUseTime = now(); } + private onSocketError(cause: Error) { + this.onError(new MongoNetworkError(cause.message, { cause })); + } + + private onTransformError(error: Error) { + this.onError(error); + } + public onError(error: Error) { this.cleanup(error); } @@ -769,7 +777,6 @@ export class Connection extends TypedEventEmitter { } finally { this.dataEvents = null; this.messageStream.pause(); - this.throwIfAborted(); } } } diff --git a/test/integration/node-specific/convert_socket_errors.test.ts b/test/integration/node-specific/convert_socket_errors.test.ts new file mode 100644 index 0000000000..0f3508632e --- /dev/null +++ b/test/integration/node-specific/convert_socket_errors.test.ts @@ -0,0 +1,78 @@ +import { Duplex } from 'node:stream'; + +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +import { Connection, type MongoClient, MongoNetworkError, ns } from '../../mongodb'; +import { clearFailPoint, configureFailPoint } from '../../tools/utils'; + +describe('Socket Errors', () => { + describe('when a socket emits an error', () => { + it('command throws a MongoNetworkError', async () => { + const socket = new Duplex(); + // @ts-expect-error: not a real socket + const connection = new Connection(socket, {}); + const testError = new Error('blah'); + socket.emit('error', testError); + const commandRes = connection.command(ns('a.b'), { ping: 1 }, {}); + + const error = await commandRes.catch(error => error); + expect(error).to.be.instanceOf(MongoNetworkError); + expect(error.cause).to.equal(testError); + }); + }); + + describe('when the sized message stream emits an error', () => { + it('command throws the same error', async () => { + const socket = new Duplex(); + // @ts-expect-error: not a real socket + const connection = new Connection(socket, {}); + const testError = new Error('blah'); + // @ts-expect-error: private field + connection.messageStream.emit('error', testError); + const commandRes = connection.command(ns('a.b'), { ping: 1 }, {}); + + const error = await commandRes.catch(error => error); + expect(error).to.equal(testError); + }); + }); + + describe('when destroyed by failpoint', () => { + let client: MongoClient; + let collection; + + const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.4' } }; + + beforeEach(async function () { + if (!this.configuration.filters.NodeVersionFilter.filter({ metadata })) { + return; + } + + await configureFailPoint(this.configuration, { + configureFailPoint: 'failCommand', + mode: 'alwaysOn', + data: { + appName: 'failInserts2', + failCommands: ['insert'], + closeConnection: true + } + }); + + client = this.configuration.newClient({}, { appName: 'failInserts2' }); + await client.connect(); + const db = client.db('closeConn'); + collection = db.collection('closeConn'); + }); + + afterEach(async function () { + sinon.restore(); + await clearFailPoint(this.configuration); + await client.close(); + }); + + it('throws a MongoNetworkError', metadata, async () => { + const error = await collection.insertOne({ name: 'test' }).catch(error => error); + expect(error, error.stack).to.be.instanceOf(MongoNetworkError); + }); + }); +}); diff --git a/test/integration/node-specific/resource_clean_up.test.ts b/test/integration/node-specific/resource_clean_up.test.ts index 1e1256f3d4..41ec740831 100644 --- a/test/integration/node-specific/resource_clean_up.test.ts +++ b/test/integration/node-specific/resource_clean_up.test.ts @@ -107,7 +107,7 @@ describe('Driver Resources', () => { await sleep(10); const promiseCountAfter = v8.queryObjects(Promise, { format: 'count' }); - expect(promiseCountAfter).to.be.within(promiseCountBefore - 5, promiseCountBefore + 5); + expect(promiseCountAfter).to.be.lessThan(promiseCountBefore + 5); }); }); });