Skip to content

Commit 13d6b68

Browse files
Nodgesindresorhus
authored andcommitted
Fix socket 'connect' memory leak (#465)
1 parent dc068ea commit 13d6b68

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

index.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,18 @@ function requestAsEventEmitter(opts) {
227227
total: uploadBodySize
228228
});
229229

230-
if (req.connection) {
231-
req.connection.once('connect', () => {
230+
const socket = req.connection;
231+
if (socket) {
232+
// `._connecting` was the old property which was made public in node v6.1.0
233+
const isConnecting = socket.connecting === undefined ? socket._connecting : socket.connecting;
234+
235+
const onSocketConnect = () => {
232236
const uploadEventFrequency = 150;
233237

234238
progressInterval = setInterval(() => {
235239
const lastUploaded = uploaded;
236240
const headersSize = Buffer.byteLength(req._header);
237-
uploaded = req.connection.bytesWritten - headersSize;
241+
uploaded = socket.bytesWritten - headersSize;
238242

239243
// Prevent the known issue of `bytesWritten` being larger than body size
240244
if (uploadBodySize && uploaded > uploadBodySize) {
@@ -254,7 +258,17 @@ function requestAsEventEmitter(opts) {
254258
total: uploadBodySize
255259
});
256260
}, uploadEventFrequency);
257-
});
261+
};
262+
263+
// Only subscribe to 'connect' event if we're actually connecting a new
264+
// socket, otherwise if we're already connected (because this is a
265+
// keep-alive connection) do not bother. This is important since we won't
266+
// get a 'connect' event for an already connected socket.
267+
if (isConnecting) {
268+
socket.once('connect', onSocketConnect);
269+
} else {
270+
onSocketConnect();
271+
}
258272
}
259273
});
260274

test/agent.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,14 @@ test('redirects from https to http work with an agent object', async t => {
134134

135135
test('socket connect listener cleaned up after request', async t => {
136136
const {agent} = createAgentSpy(HttpsAgent);
137-
await got(`${https.url}`, {
138-
rejectUnauthorized: false,
139-
agent
140-
});
137+
// Make sure there are no memory leaks when reusing keep-alive sockets
138+
for (let i = 0; i < 20; i++) {
139+
// eslint-disable-next-line no-await-in-loop
140+
await got(`${https.url}`, {
141+
rejectUnauthorized: false,
142+
agent
143+
});
144+
}
141145
Object.keys(agent.freeSockets).forEach(k => agent.freeSockets[k]
142146
.forEach(sock => t.is(sock.listenerCount('connect'), 0)));
143147
// Make sure to close all open sockets

0 commit comments

Comments
 (0)