Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Cyril Hanquez
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Cyril Hanquez@Fitzchev )

Google Chrome Will Automatically Retry Requests On Certain Error Responses

By Ben Nadel on

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!



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

@Jason,

In what context are you receiving this error? Are you building a server-side app? A client-side app?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.