Skip to content

Instantly share code, notes, and snippets.

@Jimbly
Created September 7, 2012 20:57
Show Gist options
  • Save Jimbly/3669555 to your computer and use it in GitHub Desktop.
Save Jimbly/3669555 to your computer and use it in GitHub Desktop.
Test case for getting a dead socket because of node timing issues.
var assert = require('assert');
var http = require('http');
var agent = new http.Agent({maxSockets: 1});
var headers = {'connection': 'keep-alive'};
var PORT = 8080;
var responses = 0;
var errors = 0;
setTimeout(function() {
function xticks(ticks, f) {
if (ticks === 0) {
f();
} else {
process.nextTick(xticks.bind(undefined, ticks - 1, f));
}
}
// Make the onSocket .nextTick take more than a single tick
var proto = http.ClientRequest.prototype;
proto.orig_onSocket = proto.onSocket;
proto.onSocket = function(socket) {
var req = this;
var ticks = 4;
// <=1 does not get a destroyed socket
// 2 gets a destroyed socket and two closes if we emit one, works fine if we do not emit one
// >=3 gets a destroyed socket, no node-level close emit, so this works if emitting close
xticks(ticks, req.orig_onSocket.bind(req, socket));
};
function doTry() {
var req = http.get({
path: '/', headers: headers, port: PORT, agent: agent
}, function(response) {
++responses;
var start = Date.now();
// sleep 200ms without allowing events to process
while (Date.now() - start < 200) {
}
});
req.on('socket', function(socket) {
if (socket.destroyed) {
console.log('Got DESTROYED socket');
if (!'manual fix') {
// We listen for a close event, and if in a few ticks we have not received one, then we emit one
var emitted = false;
socket.on('close', function() {
emitted = true;
});
xticks(5, function() {
if (!emitted) {
socket.emit('close');
}
});
}
} else {
console.log('Got socket');
}
});
req.on('error', function(err) {
++errors;
if (responses === 1 && errors === 1) {
console.log('Got expected error: ' + err + ', retrying...');
doTry();
} else {
console.log('Got UNEXPECTED error: ');
throw err;
}
});
}
for (var ii = 0; ii < 2; ++ii) {
doTry();
}
process.on('exit', function() {
assert.equal(responses, 2);
assert.equal(errors, 1);
});
}, 200);
var assert = require('assert');
var http = require('http');
var body = 'hello world\n';
var PORT = 8080;
var requests = 0;
var timeout_id;
var server = http.createServer(function(req, res) {
var socket = res.socket;
res.writeHead(200, {'Content-Length': body.length});
res.write(body);
res.end();
++requests;
setTimeout(function() {
socket.destroy();
if (requests === 2) {
clearTimeout(timeout_id);
server.close();
}
}, 100);
});
server.listen(PORT, function() {});
process.on('exit', function() {
assert.equal(requests, 2);
});
timeout_id = setTimeout(function() {
process.exit();
}, 3000);
@Jimbly
Copy link
Author

Jimbly commented Sep 7, 2012

This forces a reproduction of the issue reported at https://groups.google.com/forum/?fromgroups=#!topic/nodejs/k-qltQkBBnw

This is done by manually adding additional delays to the "onSocket" event handler, to effectively emulate what would have happened if the socket was actually disconnected N ticks before that event. This is not perfectly accurate since, were it actually disconnected a few ticks ago, other code may have cleaned up the socket and not passed it on, but it is effective enough to test my fix here: Jimbly/node@ea583f6

This can also be worked around with the code in the "if (!'manual fix') {" block, but you would need to add that to every place you create an HTTP Request.

Run the client and server simultaneously:
node test-http-deadsocket-server.js & node est-http-deadsocket-client.js

To test various conditions, modify "ticks" in est-http-deadsocket-client.js to values between 1 and 4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment