Skip to content

Commit f1afcc4

Browse files
authored
fix(NODE-3176): handle errors from MessageStream (#2774)
If there's a socket that gets a large amount of data written to it our MessageStream will throw an error about exceeding the permitted BSON size, this error is now captured in SDAM.
1 parent b1363c2 commit f1afcc4

File tree

2 files changed

+66
-32
lines changed

2 files changed

+66
-32
lines changed

lib/cmap/connection.js

+36-32
Original file line numberDiff line numberDiff line change
@@ -58,38 +58,9 @@ class Connection extends EventEmitter {
5858
/* ignore errors, listen to `close` instead */
5959
});
6060

61-
stream.on('close', () => {
62-
if (this.closed) {
63-
return;
64-
}
65-
66-
this.closed = true;
67-
this[kQueue].forEach(op =>
68-
op.cb(new MongoNetworkError(`connection ${this.id} to ${this.address} closed`))
69-
);
70-
this[kQueue].clear();
71-
72-
this.emit('close');
73-
});
74-
75-
stream.on('timeout', () => {
76-
if (this.closed) {
77-
return;
78-
}
79-
80-
stream.destroy();
81-
this.closed = true;
82-
this[kQueue].forEach(op =>
83-
op.cb(
84-
new MongoNetworkTimeoutError(`connection ${this.id} to ${this.address} timed out`, {
85-
beforeHandshake: this[kIsMaster] == null
86-
})
87-
)
88-
);
89-
90-
this[kQueue].clear();
91-
this.emit('close');
92-
});
61+
this[kMessageStream].on('error', error => this.handleIssue({ destroy: error }));
62+
stream.on('close', () => this.handleIssue({ isClose: true }));
63+
stream.on('timeout', () => this.handleIssue({ isTimeout: true, destroy: true }));
9364

9465
// hook the message stream up to the passed in stream
9566
stream.pipe(this[kMessageStream]);
@@ -132,6 +103,39 @@ class Connection extends EventEmitter {
132103
this[kLastUseTime] = now();
133104
}
134105

106+
/**
107+
* @param {{ isTimeout?: boolean; isClose?: boolean; destroy?: boolean | Error }} issue
108+
*/
109+
handleIssue(issue) {
110+
if (this.closed) {
111+
return;
112+
}
113+
114+
if (issue.destroy) {
115+
this[kStream].destroy(typeof issue.destroy === 'boolean' ? undefined : issue.destroy);
116+
}
117+
118+
this.closed = true;
119+
120+
for (const idAndOp of this[kQueue]) {
121+
const op = idAndOp[1];
122+
if (issue.isTimeout) {
123+
op.cb(
124+
new MongoNetworkTimeoutError(`connection ${this.id} to ${this.address} timed out`, {
125+
beforeHandshake: !!this[kIsMaster]
126+
})
127+
);
128+
} else if (issue.isClose) {
129+
op.cb(new MongoNetworkError(`connection ${this.id} to ${this.address} closed`));
130+
} else {
131+
op.cb(typeof issue.destroy === 'boolean' ? undefined : issue.destroy);
132+
}
133+
}
134+
135+
this[kQueue].clear();
136+
this.emit('close');
137+
}
138+
135139
destroy(options, callback) {
136140
if (typeof options === 'function') {
137141
callback = options;

test/unit/sdam/topology.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,35 @@ describe('Topology (unit)', function() {
291291
});
292292
});
293293
});
294+
295+
it('should encounter a server selection timeout on garbled server responses', function() {
296+
const net = require('net');
297+
const server = net.createServer();
298+
const p = Promise.resolve();
299+
server.listen(0, 'localhost', 2, () => {
300+
server.on('connection', c => c.on('data', () => c.write('garbage_data')));
301+
const address = server.address();
302+
const client = this.configuration.newClient(
303+
`mongodb://${address.address}:${address.port}`,
304+
{ serverSelectionTimeoutMS: 1000 }
305+
);
306+
p.then(() =>
307+
client
308+
.connect()
309+
.then(() => {
310+
server.close();
311+
client.close();
312+
expect.fail('Should throw a server selection error!');
313+
})
314+
.catch(error => {
315+
server.close();
316+
const closePromise = client.close();
317+
expect(error).to.exist;
318+
return closePromise;
319+
})
320+
);
321+
});
322+
return p;
323+
});
294324
});
295325
});

0 commit comments

Comments
 (0)