Skip to content

Commit 2d86095

Browse files
fix(NODE-6864): socket errors are not always converted to MongoNetworkErrors (#4473)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 28857b7 commit 2d86095

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

src/cmap/connection.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
247247
this.lastUseTime = now();
248248

249249
this.messageStream = this.socket
250-
.on('error', this.onError.bind(this))
250+
.on('error', this.onSocketError.bind(this))
251251
.pipe(new SizedMessageTransform({ connection: this }))
252-
.on('error', this.onError.bind(this));
252+
.on('error', this.onTransformError.bind(this));
253253
this.socket.on('close', this.onClose.bind(this));
254254
this.socket.on('timeout', this.onTimeout.bind(this));
255255

@@ -304,6 +304,14 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
304304
this.lastUseTime = now();
305305
}
306306

307+
private onSocketError(cause: Error) {
308+
this.onError(new MongoNetworkError(cause.message, { cause }));
309+
}
310+
311+
private onTransformError(error: Error) {
312+
this.onError(error);
313+
}
314+
307315
public onError(error: Error) {
308316
this.cleanup(error);
309317
}
@@ -769,7 +777,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
769777
} finally {
770778
this.dataEvents = null;
771779
this.messageStream.pause();
772-
this.throwIfAborted();
773780
}
774781
}
775782
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Duplex } from 'node:stream';
2+
3+
import { expect } from 'chai';
4+
import * as sinon from 'sinon';
5+
6+
import { Connection, type MongoClient, MongoNetworkError, ns } from '../../mongodb';
7+
import { clearFailPoint, configureFailPoint } from '../../tools/utils';
8+
9+
describe('Socket Errors', () => {
10+
describe('when a socket emits an error', () => {
11+
it('command throws a MongoNetworkError', async () => {
12+
const socket = new Duplex();
13+
// @ts-expect-error: not a real socket
14+
const connection = new Connection(socket, {});
15+
const testError = new Error('blah');
16+
socket.emit('error', testError);
17+
const commandRes = connection.command(ns('a.b'), { ping: 1 }, {});
18+
19+
const error = await commandRes.catch(error => error);
20+
expect(error).to.be.instanceOf(MongoNetworkError);
21+
expect(error.cause).to.equal(testError);
22+
});
23+
});
24+
25+
describe('when the sized message stream emits an error', () => {
26+
it('command throws the same error', async () => {
27+
const socket = new Duplex();
28+
// @ts-expect-error: not a real socket
29+
const connection = new Connection(socket, {});
30+
const testError = new Error('blah');
31+
// @ts-expect-error: private field
32+
connection.messageStream.emit('error', testError);
33+
const commandRes = connection.command(ns('a.b'), { ping: 1 }, {});
34+
35+
const error = await commandRes.catch(error => error);
36+
expect(error).to.equal(testError);
37+
});
38+
});
39+
40+
describe('when destroyed by failpoint', () => {
41+
let client: MongoClient;
42+
let collection;
43+
44+
const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.4' } };
45+
46+
beforeEach(async function () {
47+
if (!this.configuration.filters.NodeVersionFilter.filter({ metadata })) {
48+
return;
49+
}
50+
51+
await configureFailPoint(this.configuration, {
52+
configureFailPoint: 'failCommand',
53+
mode: 'alwaysOn',
54+
data: {
55+
appName: 'failInserts2',
56+
failCommands: ['insert'],
57+
closeConnection: true
58+
}
59+
});
60+
61+
client = this.configuration.newClient({}, { appName: 'failInserts2' });
62+
await client.connect();
63+
const db = client.db('closeConn');
64+
collection = db.collection('closeConn');
65+
});
66+
67+
afterEach(async function () {
68+
sinon.restore();
69+
await clearFailPoint(this.configuration);
70+
await client.close();
71+
});
72+
73+
it('throws a MongoNetworkError', metadata, async () => {
74+
const error = await collection.insertOne({ name: 'test' }).catch(error => error);
75+
expect(error, error.stack).to.be.instanceOf(MongoNetworkError);
76+
});
77+
});
78+
});

test/integration/node-specific/resource_clean_up.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('Driver Resources', () => {
107107
await sleep(10);
108108
const promiseCountAfter = v8.queryObjects(Promise, { format: 'count' });
109109

110-
expect(promiseCountAfter).to.be.within(promiseCountBefore - 5, promiseCountBefore + 5);
110+
expect(promiseCountAfter).to.be.lessThan(promiseCountBefore + 5);
111111
});
112112
});
113113
});

0 commit comments

Comments
 (0)