Skip to content

Commit 959bdfd

Browse files
refackFishrock123
authored andcommitted
http: guard against failed sockets creation
PR-URL: #13839 Fixes: #13045 Fixes: #13831 Refs: #13352 Refs: #13548 (comment) Reviewed-By: Trevor Norris <[email protected]>
1 parent d174264 commit 959bdfd

File tree

2 files changed

+72
-47
lines changed

2 files changed

+72
-47
lines changed

lib/_http_agent.js

+19-18
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/,
181181
} else if (sockLen < this.maxSockets) {
182182
debug('call onSocket', sockLen, freeLen);
183183
// If we are under maxSockets create a new one.
184-
this.createSocket(req, options, function(err, newSocket) {
185-
if (err) {
186-
nextTick(newSocket._handle.getAsyncId(), function() {
187-
req.emit('error', err);
188-
});
189-
return;
190-
}
191-
req.onSocket(newSocket);
192-
});
184+
this.createSocket(req, options, handleSocketCreation(req, true));
193185
} else {
194186
debug('wait for socket');
195187
// We are over limit so we'll add it to the queue.
@@ -222,6 +214,7 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
222214
const newSocket = self.createConnection(options, oncreate);
223215
if (newSocket)
224216
oncreate(null, newSocket);
217+
225218
function oncreate(err, s) {
226219
if (called)
227220
return;
@@ -294,15 +287,7 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
294287
debug('removeSocket, have a request, make a socket');
295288
var req = this.requests[name][0];
296289
// If we have pending requests and a socket gets closed make a new one
297-
this.createSocket(req, options, function(err, newSocket) {
298-
if (err) {
299-
nextTick(newSocket._handle.getAsyncId(), function() {
300-
req.emit('error', err);
301-
});
302-
return;
303-
}
304-
newSocket.emit('free');
305-
});
290+
this.createSocket(req, options, handleSocketCreation(req, false));
306291
}
307292
};
308293

@@ -332,6 +317,22 @@ Agent.prototype.destroy = function destroy() {
332317
}
333318
};
334319

320+
function handleSocketCreation(request, informRequest) {
321+
return function handleSocketCreation_Inner(err, socket) {
322+
if (err) {
323+
const asyncId = (socket && socket._handle && socket._handle.getAsyncId) ?
324+
socket._handle.getAsyncId() :
325+
null;
326+
nextTick(asyncId, () => request.emit('error', err));
327+
return;
328+
}
329+
if (informRequest)
330+
request.onSocket(socket);
331+
else
332+
socket.emit('free');
333+
};
334+
}
335+
335336
module.exports = {
336337
Agent,
337338
globalAgent: new Agent()

test/parallel/test-http-agent.js

+53-29
Original file line numberDiff line numberDiff line change
@@ -20,41 +20,65 @@
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
'use strict';
23-
require('../common');
23+
const common = require('../common');
2424
const assert = require('assert');
2525
const http = require('http');
2626

27-
const server = http.Server(function(req, res) {
28-
res.writeHead(200);
29-
res.end('hello world\n');
30-
});
31-
32-
let responses = 0;
3327
const N = 4;
3428
const M = 4;
29+
const server = http.Server(common.mustCall(function(req, res) {
30+
res.writeHead(200);
31+
res.end('hello world\n');
32+
}, (N * M))); // N * M = good requests (the errors will not be counted)
3533

36-
server.listen(0, function() {
37-
const port = this.address().port;
38-
for (let i = 0; i < N; i++) {
39-
setTimeout(function() {
40-
for (let j = 0; j < M; j++) {
41-
http.get({ port: port, path: '/' }, function(res) {
42-
console.log('%d %d', responses, res.statusCode);
43-
if (++responses === N * M) {
44-
console.error('Received all responses, closing server');
45-
server.close();
46-
}
47-
res.resume();
48-
}).on('error', function(e) {
49-
console.log('Error!', e);
50-
process.exit(1);
51-
});
34+
function makeRequests(outCount, inCount, shouldFail) {
35+
let responseCount = outCount * inCount;
36+
let onRequest = common.mustNotCall(); // Temporary
37+
const p = new Promise((resolve) => {
38+
onRequest = common.mustCall((res) => {
39+
if (--responseCount === 0) {
40+
server.close();
41+
resolve();
5242
}
53-
}, i);
54-
}
55-
});
43+
if (!shouldFail)
44+
res.resume();
45+
}, outCount * inCount);
46+
});
47+
48+
server.listen(0, () => {
49+
const port = server.address().port;
50+
for (let i = 0; i < outCount; i++) {
51+
setTimeout(() => {
52+
for (let j = 0; j < inCount; j++) {
53+
const req = http.get({ port: port, path: '/' }, onRequest);
54+
if (shouldFail)
55+
req.on('error', common.mustCall(onRequest));
56+
else
57+
req.on('error', (e) => assert.fail(e));
58+
}
59+
}, i);
60+
}
61+
});
62+
return p;
63+
}
5664

65+
const test1 = makeRequests(N, M);
5766

58-
process.on('exit', function() {
59-
assert.strictEqual(N * M, responses);
60-
});
67+
const test2 = () => {
68+
// Should not explode if can not create sockets.
69+
// Ref: https://github.com/nodejs/node/issues/13045
70+
// Ref: https://github.com/nodejs/node/issues/13831
71+
http.Agent.prototype.createConnection = function createConnection(_, cb) {
72+
process.nextTick(cb, new Error('nothing'));
73+
};
74+
return makeRequests(N, M, true);
75+
};
76+
77+
test1
78+
.then(test2)
79+
.catch((e) => {
80+
// This is currently the way to fail a test with a Promise.
81+
console.error(e);
82+
process.exit(1);
83+
}
84+
);

0 commit comments

Comments
 (0)