Skip to content

Instantly share code, notes, and snippets.

@williamkapke
Last active December 22, 2015 13:09
Show Gist options
  • Save williamkapke/6477189 to your computer and use it in GitHub Desktop.
Save williamkapke/6477189 to your computer and use it in GitHub Desktop.

The problem:

A forked process ends connections when these 2 conditions exist in the child:

  1. The child process thread takes longer than ~2 seconds to start.
  2. The child makes a http request.

NOTES:

  • This is only happens to the sockets that are given to the child before the child is done loading.
  • I'm running node v0.10.18 and have not tried on any other versions (yet).
  • NODE_DEBUG="net http" didn't reveal anything helpful.
  • This is on OSX 10.7.5

To run the demo:

Run in terminal #1:

node nag.js

Run in terminal #2:

node app.js

Once you start to see the ISO dates printing, the demo is over and you can terminate everything.

nag.js

The purpose of nag is to simulate the reconnect behavior of an EventSource. By eliminating the browser, I ruled out this being some kind of problem with Chrome's EventSource implementation.

The output

$ node nag
connecting...ECONNREFUSED.client closed
connecting...ECONNREFUSED.client closed
connecting...ECONNREFUSED.client closed
connecting...connected.client closed
connecting...connected.client closed
connecting...connected.client closed
connecting...connected.client closed
connecting...connected.
Yo! Want to play with matches?
client closed
connecting...connected.
HEY! Why did you run off like that!?!?!?

data:2013-09-07T16:02:18.144Z
data:2013-09-07T16:02:19.226Z
data:2013-09-07T16:02:20.304Z

app.js & child.js

To create this isolated issue, I started with the Example: sending socket object from the 'child_process` docs which hands sockets off a child processes to handle.

It simulates my situation where requests are only handled if a child process exists. A child process is created when a scheduled task creates it. Once created and doing it's job- it streams events to any sockets that are listening.

The output

$ node app
new request
T minus 3
new request
T minus 2
new request
T minus 1
new request
gotime!
The child is born
new request
modules loaded
parent says 'talk to your riend'
have fun kids
He doesn't want to play anymore :(

Google responded: 200
new request
parent says 'talk to your friend'
The cat came back
have fun kids
Google responded: 200
Google responded: 200

var gotime;
var http = require('http');
http.createServer(function (req, res) {
console.log('new request');
if(gotime){
gotime.send('talk to your friend', res.socket);
}
else {
res.useChunkedEncodingByDefault = false;
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Content-Type":"text/event-stream",
"Cache-Control":"no-cache",
"Connection": "close"
});
res.end();
}
})
.listen(4000);
//pretend something schedules firing off some work
var count = 4;
function initiator(){
if(--count){
console.log("T minus "+count);
setTimeout(initiator, 1000);
}
else {
console.log("gotime!");
gotime = require('child_process').fork('child.js', ['chester']);
}
}
setTimeout(initiator, 1000);
console.log("The child is born");
var http = require("http");
var visited = false;
process.on('message', function(m, socket) {
console.log("parent says '" + m +"'");
socket.setTimeout(0);
socket.emit("agentRemove");
socket.write('HTTP/1.1 200 OK\n');
socket.write('Access-Control-Allow-Origin: *\n');
socket.write('Content-Type: text/event-stream\n');
socket.write('Cache-Control: no-cache\n');
socket.write('Connection: close\n\n');
socket.on('close', function(){
console.log("He doesn't want to play anymore :(");
toy.removeListener('play', fun);
});
//Nothing in this code terminates the connection- but it somehow does.
if(visited){
socket.write("HEY! Why did you run off like that!?!?!?");
console.log("The cat came back");
}
else {
visited = true;
socket.write('Yo! Want to play with matches?');
}
function fun(when) {
socket.write('data:'+when);
}
toy.on('play', fun);
console.log('have fun kids');
});
//simulate loading modules that are slow. like: require("./bigdata.geojson")
// In my testing, the delay must be about 2 second or more. Going higher
// doesn't make it drop more times... just always the first time.
var d = 2000 + +new Date();
while(new Date()<d);
console.log("modules loaded");
//simulate fetching things and reporting back to the socket.
var toy = new (require("events").EventEmitter)();
function play() {
http.get("http://www.google.com", function(res) {
console.log("Google responded: " + res.statusCode);
toy.emit('play', new Date().toISOString());
setTimeout(play, 1000);
})
}
play();
// This opens a connection to the socket created in app.js.
// If there is any problem, it will try to reconnect over and over
// This is to simulate how `EventSource`s work.
var http = require('http');
var time = 1000;
function connect(){
process.stdout.write("connecting...");
http.get({hostname:'localhost', port:4000, path:'/', agent:false}, function (res) {
process.stdout.write("connected.");
res.setEncoding('utf8');
var first = true;
res.on('data', function (chunk) {
if(first) { first = false; console.log(); }
console.log(chunk.toString());
});
})
.on('error', function(e) {
if(e.code==='ECONNREFUSED'){
process.stdout.write("ECONNREFUSED.");
return;
}
console.trace(e);
})
.on('close', function() {
console.log('client closed');
setTimeout(connect, time);
})
.setNoDelay(true);
}
connect();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment