Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Winnie Tong
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Winnie Tong

Google Chrome Will Automatically Retry Requests On Certain Error Responses

By
Published in Comments (2)

The other day, when I was trying to understand how the Node.js Cluster module reacts to Worker process disconnects, I was seeing some baffling behavior in my error logging. For every request that I was making to the Node.js server, I was seeing three errors show up in my console. At first, I thought it was a problem with my code; but, eventually, I realized that the Google Chrome browser was performing automatic retry attempts when it received an ERR_CONNECTION_REFUSED response or an ERR_EMPTY_RESPONSE response. The thing that tipped me off to this - aside from the unique Worker PIDs - was the fact that Google Chrome won't exhibit this behavior if the Dev Tools are open.

NOTE: I saw this behavior with the ERR_CONNECTION_REFUSED and ERR_EMPTY_RESPONSE errors; though, there's no reason to assume that this is an exhaustive list of errors that exhibit this behavior.

This behavior can be easily reproduced with a simple Master / Worker configuration in which the Worker process exits-out immediately upon receiving a request:

// Require the core node modules.
var chalk = require( "chalk" );
var cluster = require( "cluster" );
var http = require( "http" );
var os = require( "os" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// MASTER PROCESS.
// --
// The Master process, in the cluster module, is responsible for spawning child-
// processes that all share the same port-listening. The Master will round-robin (on
// most systems) requests to the child / worker processes.
if ( cluster.isMaster ) {

	console.log( chalk.red.bold( "[Cluster]" ), "Master is now running.", process.pid );

	// Ensure that there are at least 2 workers to enable redundancy and availability.
	// This way, if one process dies, there should always be another process ready
	// to take on work.
	var concurrencyCount = Math.max( 2, ( process.env.WEB_CONCURRENCY || os.cpus().length ) );

	// Spawn child / worker processes.
	for ( var i = 0 ; i < concurrencyCount ; i++ ) {

		cluster.fork();

	}

	// Listen for Worker process death.
	cluster.on(
		"exit",
		function handleExit( worker, code, signal ) {

			console.log( chalk.red.bold( "[Cluster]" ), "Worker has exited.", worker.process.pid );

		}
	);

// WORKER PROCESS.
// --
// The Worker process, in the cluster module, is responsible for implementing the actual
// request handling for the application. Each Worker is a completely separate instance
// of the application and shares no memory with either the Master or the other Workers.
} else {

	console.log( chalk.red( "[Worker]" ), "Worker has started.", process.pid );

	// Setup the application server.
	http
		.createServer(
			function( request, response ) {

				// For this demo, we're just going to exit the Worker immediately so we
				// can see how the Browser reacts to this type of failure.
				// --
				// NOTE: Produces the ERR_CONNECTION_REFUSED error response.
				process.exit( 1 );

				// NOTE: If we threw an uncaught error, instead of exiting, we'd get
				// the ERR_EMPTY_RESPONSE error response.

			}
		)
		.listen( 3000 )
	;

}

As you can see, the Master process simply spawns Worker processes and then listens for process death. The Worker process, on the other hand, sets up the HTTP servers and then exists-out immediately upon request.

Now, if we run this in Google Chrome with the dev tools closed, we see the following behavior exhibited:

Google Chrome retry on error with Dev Tools closed.

As you can see, the Google Chrome browser appears to be making automatic retry requests when the Node.js server fails to return a response. In this case, the first request fails and then Google Chrome makes two more requests.

Now, if we open the Dev Tools and run the same demo, we get different behavior:

Google Chrome does not retry on error when Dev Tools are open.

As you can see this time, when the Dev Tools are open, the Google Chrome browser only makes a single request to the Node.js server, causing only one of the Worker processes to die. Perhaps this is a setting somewhere in the Dev Tools configuration. Or, perhaps the browser does this so as to not make the Network activity tab confusing.

Anyway, this was basically just a short "Note to Self" so I can better understand and remember why I might see unusual request activity in my Node.js error logs. I'm still trying to wrap my head around how Master and Worker processes interact when it comes to process death; but, at least going forward, I'll know to always run my experiments with my Chrome Dev Tools open!

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel