Nick
Nick

Reputation: 19664

Why is server.close callback never invoked?

I am trying to 'gracefully' close a net.Server instance (created with app.listen()) if an un-handled error is thrown. Server creation occurs in my bin/www script. All error handling and routing middleware configuration is defined in index.js.

In my application configuration module (index.js) I have error handling middleware that checks that each error is handled. If the error is not handled then a 'close' event is emitted.

Note: Each req and res is wrapped in a domain. I am using express-domain-middleware middleware module to listen for error events on each req domain and route the error to my error handling. I only mention this in case it might be the culprit.

The 'close_server' event handler should:

  1. Close the server so new connections are not accepted.
  2. Close the current process once all open connections have completed.
  3. If after 10 seconds the server has not closed, force the process to close.

The optional callback provided to server.close() never seems to be invoked and I'm not sure why. To test this I am making a single request which throws an error. The process is only closed after the timer expires (10 seconds has elapsed).

Could there be something holding open a connection in the server? Why is the server.close() callback never called?

Thanks!

Update I was using Chrome to make a request to the server. It appears that the browser is holding open a connection. If I make the request using curl it works as expected.

See this issue

index.js

     app.use(function (err, req, res, next) {
        if (typeof err.statusCode !== 'undefined') {
            if (err.statusCode >= 500) {
                Logger.error({error: err});
                return next(err);
            } else {
                Logger.warn({warn: err});
                return next(err);
            }
        } else {
            //The error is un-handled and the server needs to go bye, bye
            var unhandledError = new UnhandledError(util.format('%s:%s', req.method, req.originalUrl), 'Server shutting down!', err.stack, 500);
            Logger.fatal({fatal: unhandledError});
            res.status(500).send('Server Error');
            app.emit('close_server', unhandledError);
        }
    });

bin/www

#!/usr/bin/env node
var app = require('../index');    
var port = config.applicationPort;

app.set('port', port);
var server = app.listen(app.get('port'));

/*
 * Wait for open connections to complete and shut server down.
 * After 10 seconds force process to close.
 * */
app.on('close_server', function () {
    server.close(function () {
        console.log('Server Closed.');
        process.exit()
    });
    setTimeout(function () {
        console.log('Force Close.');
        process.exit()
    }, 10 * 1000);
});

Upvotes: 7

Views: 3053

Answers (2)

As @mscdex mentioned server.close never runs its callback when the browser sends the request Connection: keep-alive, because server.close only stops the server from accepting new connections.

Node v18.2.0 introduced server.closeAllConnections() and server.closeIdleConnections()

server.closeAllConnections() closes all connections connected to the server, and server.closeIdleConnections() closes all connections connected to the server but only the ones which are not sending a request or waiting for a response.

Before Node v18.2.0 I tackled this problem by waiting 5 seconds for the server to shutdown, after which it would force exit.

The following code contemplates both situations

process.on('SIGINT', gracefulShutdown)
process.on('SIGTERM', gracefulShutdown)

function gracefulShutdown (signal) {
  if (signal) {
    console.log(`\nReceived signal ${signal}`)
  }
  console.log('Gracefully closing http server')

  // closeAllConnections() is only available after Node v18.02
  if (server.closeAllConnections) server.closeAllConnections()
  else setTimeout(() => process.exit(0), 5000)

  try {
    server.close((err) => {
      if (err) {
        console.error(err)
        process.exit(1)
      } else {
        console.log('http server closed successfully. Exiting!')
        process.exit(0)
      }
    })
  } catch (err) {
    console.error('There was an error', err)
    setTimeout(() => process.exit(1), 500)
  }
}

Upvotes: 3

mscdex
mscdex

Reputation: 106698

server.close() does not close open client connections, it only stops accepting new connections.

So most likely Chrome is sending a request with Connection: keep-alive which means the connection stays open for some time for efficiency reasons (to be able to make multiple requests on the same connection), whereas curl is probably using Connection: close where the connection is severed immediately after the server's response.

Upvotes: 6

Related Questions