Skip to content

Instantly share code, notes, and snippets.

@soletan
Created September 27, 2015 14:40
Show Gist options
  • Save soletan/701fefc7727c2bc7c170 to your computer and use it in GitHub Desktop.
Save soletan/701fefc7727c2bc7c170 to your computer and use it in GitHub Desktop.
How to shut down express js gracefully while supporting keep-alive
// this is an excerpt of essential but non-working part extracted from local expressjs application ...
var app = require( "express" )();
var httpServer = require( "http" ).createServer( app );
httpServer.on( "connection", function( socket ) {
socket.unref();
} );
httpServer.listen( CONFIG.port, CONFIG.ip );
app.use( function _gracefulShutdownSupporter( req, res, next ) {
// mark connection to be actively serving request now
req.connection.ref();
// wrap genuine end() on response to clear mark afterwards
res._genuineEnd = res.end;
res.end = function() {
var args = [], i, l, a, injected = false,
socket = req.connection;
for ( i = 0, l = arguments.length, args = []; i < l; i++ ) {
a = arguments[i];
if ( typeof a === "function" && !injected ) {
injected = true;
args[i] = (function( genuine ) {
return function() {
socket.unref();
return genuine.apply( this, arguments );
};
})( a );
} else {
args[i] = a;
}
}
if ( !injected ) {
args.push( function() {
socket.unref();
} );
}
return res._genuineEnd.apply( this, args );
};
next();
} );
@soletan
Copy link
Author

soletan commented Sep 27, 2015

Shut down is performed like this:

var shuttingDown = false;

function _shutdownService() {
    if ( !shuttingDown ) {
        shuttingDown = true;

        var shutdowns = 0;

        if ( httpServer ) {
            console.log( "closing http listener" );
            shutdown( httpServer );
        }

        // [..]
    }

    function shutdown( service ) {
        shutdowns++;

        service.on( "close", _onServersClosed );
        service.close();
    }

    function _onServersClosed() {
        if ( --shutdowns === 0 ) {
            process.exit();
        }
    }
}

process.on( "SIGINT", _shutdownService );
process.on( "SIGTERM", _shutdownService );

if ( process.platform === "win32" ) {
    var rl = require( "readline" ).createInterface( {
        input: process.stdin,
        output: process.stdout
    } );

    rl.on( "SIGINT", function() {
        rl.close();
        process.emit( "SIGINT" );
    } );
}

Code has been tested on Win32 platform.

@soletan
Copy link
Author

soletan commented Sep 27, 2015

Shutdown works instantly unless having processed any request. It takes a time if shutdown was initiated shortly after having processed requests.

@soletan
Copy link
Author

soletan commented Sep 28, 2015

Tested behaviour using this script ...

var http = require( "http" );
var app = require( "express" )();

app.get( "/", function( req, res ) {
    req.connection.ref();

    res.end( "Ho!", function() {
        req.connection.unref();
    } );
} );

var server = http.createServer( app );
server.listen( 8080 );

var server2 = http.createServer( app );
server2.listen( 8081 );

function _shutdownService() {
    var c = 2;

    console.log( "service got SIGINT/SIGTERM, shutting down" );

    process.on( "beforeExit", function() { console.log( "exiting" ); } );

    server.on( "close", function _onServersClosed() {
        console.log( "listener 1 closed" );

        if ( !--c ) {
            process.exit();
        }
    } );

    server2.on( "close", function _onServersClosed() {
        console.log( "listener 2 closed" );

        if ( !--c ) {
            process.exit();
        }
    } );

    server.close();
    server2.close();
    console.log( "shut-down initiated" );
}

process.on( "SIGINT", _shutdownService );

if ( process.platform === "win32" ) {
    var rl = require( "readline" ).createInterface( {
        input: process.stdin,
        output: process.stdout
    } );

    rl.on( "SIGINT", function() {
        rl.close();
        process.emit( "SIGINT" );
    } );
}

Test includes fetching from port 8080 several times. Instant output on pressing Ctrl+C in console was:

service got SIGINT/SIGTERM, shutting down
shut-down initiated
listener 2 closed
exiting

Basically, it looks like success, but listener 1 - the used one - didn't get close event before exiting application.

@soletan
Copy link
Author

soletan commented Sep 28, 2015

Obviously, server is behaving differently (e.g. by not emitting "close" event) in case of using ref()/unref() on sockets.

Relying on a separate collection of used sockets and tracking their activity is working though:

var http = require( "http" );
var app = require( "express" )();

var sockets = {}, nextId = 1;

app.get( "/", function( req, res ) {
    var socket = req.connection,
        id = socket._socketsQueueId;

    if ( !id ) {
        id = socket._socketsQueueId = nextId++;
        sockets[id] = socket;
        socket.on( "close", function() {
            sockets[id] = undefined;
        } );
    }

    socket._currentlyActive = true;

    res.end( "Ho!", function() {
        socket._currentlyActive = false;
    } );
} );

var server = http.createServer( app );
server.listen( 8080 );

var server2 = http.createServer( app );
server2.listen( 8081 );

function _shutdownService() {
    var c = 2;

    console.log( "service got SIGINT/SIGTERM, shutting down" );

    process.on( "beforeExit", function() { console.log( "exiting" ); } );

    server.on( "close", function _onServersClosed() {
        console.log( "listener 1 closed, %d left", --c );

        if ( c === 0 ) {
            console.log( "trigger exit" );
            process.exit();
        }
    } );

    server2.on( "close", function _onServersClosed() {
        console.log( "listener 2 closed, %d left", --c );

        if ( c === 0 ) {
            console.log( "trigger exit" );
            process.exit();
        }
    } );

    Object.keys( sockets )
        .forEach( function( id ) {
            var socket = sockets[id];

            if ( socket && !socket._currentlyActive ) {
                socket.setTimeout( 100, function() {
                    socket.end();
                    socket.destroy();
                } );
            }
        } );

    server.close();
    server2.close();

    console.log( "shut-down initiated" );
}

process.on( "SIGINT", _shutdownService );

if ( process.platform === "win32" ) {
    var rl = require( "readline" ).createInterface( {
        input: process.stdin,
        output: process.stdout
    } );

    rl.on( "SIGINT", function() {
        rl.close();
        process.emit( "SIGINT" );
    } );
}

Though requesting on either port 8080 and 8081, the service stops instantly on pressing Ctrl+C in Windows terminal writing:

service got SIGINT/SIGTERM, shutting down
shut-down initiated
listener 2 closed, 1 left
listener 1 closed, 0 left
trigger exit

Will try to inject this solution into my initial application now.

@soletan
Copy link
Author

soletan commented Sep 28, 2015

As a final note, gracefully shutting down expressjs application (involving database connection) was working finally using the latest approach given above.

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