Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix socket 'connect' listener handling #2439

Merged
merged 1 commit into from Nov 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 16 additions & 2 deletions request.js
Expand Up @@ -766,8 +766,15 @@ Request.prototype.start = function () {
self.emit('drain')
})
self.req.on('socket', function(socket) {
if (timeout !== undefined) {
socket.once('connect', function() {
// `._connecting` was the old property which was made public in node v6.1.0
var isConnecting = socket._connecting || socket.connecting
// Only start the connection timer if we're actually connecting a new
// socket, otherwise if we're already connected (because this is a
// keep-alive connection) do not bother. This is important since we won't
// get a 'connect' event for an already connected socket.
if (timeout !== undefined && isConnecting) {
var onReqSockConnect = function() {
socket.removeListener('connect', onReqSockConnect)
clearTimeout(self.timeoutTimer)
self.timeoutTimer = null
// Set an additional timeout on the socket, via the `setsockopt` syscall.
Expand All @@ -785,13 +792,20 @@ Request.prototype.start = function () {
self.emit('error', e)
}
})
}

socket.on('connect', onReqSockConnect)

self.req.on('error', function(err) {
socket.removeListener('connect', onReqSockConnect)
})

// Set a timeout in memory - this block will throw if the server takes more
// than `timeout` to write the HTTP status and headers (corresponding to
// the on('response') event on the client). NB: this measures wall-clock
// time, not the time between bytes sent by the server.
self.timeoutTimer = setTimeout(function () {
socket.removeListener('connect', onReqSockConnect)
self.abort()
var e = new Error('ETIMEDOUT')
e.code = 'ETIMEDOUT'
Expand Down
47 changes: 47 additions & 0 deletions tests/test-timeout.js
Expand Up @@ -6,6 +6,19 @@ function checkErrCode(t, err) {
'Error ETIMEDOUT or ESOCKETTIMEDOUT')
}

function checkEventHandlers(t, socket) {
var connectListeners = socket.listeners('connect')
var found = false
for (var i = 0; i < connectListeners.length; ++i) {
var fn = connectListeners[i]
if (typeof fn === 'function' && fn.name === 'onReqSockConnect') {
found = true
break
}
}
t.ok(!found, 'Connect listener should not exist')
}

var server = require('./server')
, request = require('../index')
, tape = require('tape')
Expand Down Expand Up @@ -75,10 +88,14 @@ tape('should not timeout', function(t) {
timeout: 1200
}

var socket
request(shouldntTimeout, function(err, res, body) {
t.equal(err, null)
t.equal(body, 'waited')
checkEventHandlers(t, socket)
t.end()
}).on('socket', function(socket_) {
socket = socket_
})
})

Expand Down Expand Up @@ -131,10 +148,40 @@ tape('connect timeout', function(t) {
url: tarpitHost + '/timeout',
timeout: 100
}
var socket
request(shouldConnectTimeout, function(err) {
checkErrCode(t, err)
t.ok(err.connect === true, 'Connect Timeout Error should set \'connect\' property to true')
checkEventHandlers(t, socket)
t.end()
}).on('socket', function(socket_) {
socket = socket_
})
})

tape('connect timeout with non-timeout error', function(t) {
// We need a destination that will not immediately return a TCP Reset
// packet. StackOverflow suggests this host:
// https://stackoverflow.com/a/904609/329700
var tarpitHost = 'http://10.255.255.1'
var shouldConnectTimeout = {
url: tarpitHost + '/timeout',
timeout: 1000
}
var socket
request(shouldConnectTimeout, function(err) {
t.notEqual(err, null)
// Delay the check since the 'connect' handler is removed in a separate
// 'error' handler which gets triggered after this callback
setImmediate(function() {
checkEventHandlers(t, socket)
t.end()
})
}).on('socket', function(socket_) {
socket = socket_
setImmediate(function() {
socket.emit('error', new Error('Fake Error'))
})
})
})

Expand Down