Skip to content

Commit 76b110e

Browse files
authored
fix(NODE-3176): handle errors from MessageStream (#2780)
1 parent 2e3335b commit 76b110e

File tree

2 files changed

+60
-32
lines changed

2 files changed

+60
-32
lines changed

src/cmap/connection.ts

+32-32
Original file line numberDiff line numberDiff line change
@@ -194,38 +194,9 @@ export class Connection extends EventEmitter {
194194
/* ignore errors, listen to `close` instead */
195195
});
196196

197-
stream.on('close', () => {
198-
if (this.closed) {
199-
return;
200-
}
201-
202-
this.closed = true;
203-
this[kQueue].forEach(op =>
204-
op.cb(new MongoNetworkError(`connection ${this.id} to ${this.address} closed`))
205-
);
206-
this[kQueue].clear();
207-
208-
this.emit('close');
209-
});
210-
211-
stream.on('timeout', () => {
212-
if (this.closed) {
213-
return;
214-
}
215-
216-
stream.destroy();
217-
this.closed = true;
218-
this[kQueue].forEach(op =>
219-
op.cb(
220-
new MongoNetworkTimeoutError(`connection ${this.id} to ${this.address} timed out`, {
221-
beforeHandshake: this[kIsMaster] == null
222-
})
223-
)
224-
);
225-
226-
this[kQueue].clear();
227-
this.emit('close');
228-
});
197+
this[kMessageStream].on('error', error => this.handleIssue({ destroy: error }));
198+
stream.on('close', () => this.handleIssue({ isClose: true }));
199+
stream.on('timeout', () => this.handleIssue({ isTimeout: true, destroy: true }));
229200

230201
// hook the message stream up to the passed in stream
231202
stream.pipe(this[kMessageStream]);
@@ -269,6 +240,35 @@ export class Connection extends EventEmitter {
269240
this[kLastUseTime] = now();
270241
}
271242

243+
handleIssue(issue: { isTimeout?: boolean; isClose?: boolean; destroy?: boolean | Error }): void {
244+
if (this.closed) {
245+
return;
246+
}
247+
248+
if (issue.destroy) {
249+
this[kStream].destroy(typeof issue.destroy === 'boolean' ? undefined : issue.destroy);
250+
}
251+
252+
this.closed = true;
253+
254+
for (const [, op] of this[kQueue]) {
255+
if (issue.isTimeout) {
256+
op.cb(
257+
new MongoNetworkTimeoutError(`connection ${this.id} to ${this.address} timed out`, {
258+
beforeHandshake: !!this[kIsMaster]
259+
})
260+
);
261+
} else if (issue.isClose) {
262+
op.cb(new MongoNetworkError(`connection ${this.id} to ${this.address} closed`));
263+
} else {
264+
op.cb(typeof issue.destroy === 'boolean' ? undefined : issue.destroy);
265+
}
266+
}
267+
268+
this[kQueue].clear();
269+
this.emit('close');
270+
}
271+
272272
destroy(): void;
273273
destroy(callback: Callback): void;
274274
destroy(options: DestroyOptions): void;

test/unit/sdam/topology.test.js

+28
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,33 @@ describe('Topology (unit)', function () {
286286
});
287287
});
288288
});
289+
290+
it('should encounter a server selection timeout on garbled server responses', function () {
291+
const net = require('net');
292+
const server = net.createServer();
293+
const p = Promise.resolve();
294+
server.listen(0, 'localhost', 2, () => {
295+
server.on('connection', c => c.on('data', () => c.write('garbage_data')));
296+
const { address, port } = server.address();
297+
const client = this.configuration.newClient(`mongodb://${address}:${port}`, {
298+
serverSelectionTimeoutMS: 1000
299+
});
300+
p.then(() =>
301+
client
302+
.connect()
303+
.then(() => {
304+
expect.fail('Should throw a server selection error!');
305+
})
306+
.catch(error => {
307+
expect(error).to.exist;
308+
})
309+
.finally(() => {
310+
server.close();
311+
return client.close();
312+
})
313+
);
314+
});
315+
return p;
316+
});
289317
});
290318
});

0 commit comments

Comments
 (0)