Graceful shutdown with nodemon and express
Asked Answered
L

2

7

I am struggling to intercept the signal when nodemon is restarting my express server. I need this in order to close the database which otherwise throws an error when I try to use it next. EDIT: it didn't look like it at first but it does call some of the functions when I terminate it via Ctrl+C. I've commented which ones.

Apparently, nodemon sends the SIGUSR2 signal when it is restarting, but I tried adding an event to that as well as countless others; This is an excerpt of the file nodemon is being told to start (main entry point of the application in a file called /bin/www, this was the default when I created the express application); as you can see I tried a bunch of things:

var app = require("../app");
var debug = require("debug")("server:server");
var http = require("http");
// terminus was built to handle this, right?
const { createTerminus } = require("@godaddy/terminus");


app.set("port", "3001");

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

// the terminus handler for SIGUSR2
function onSignal() {
    console.log("server is starting cleanup");
    // start cleanup of resource, like databases or file descriptors
}

async function onHealthCheck() {
    // checks if the system is healthy, like the db connection is live
    // resolves, if health, rejects if not
    return true;
}

createTerminus(server, {
    signal: "SIGUSR2",
    healthChecks: { "/healthcheck": onHealthCheck },
    onSignal
});

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on("error", onError);
server.on("listening", onListening);
server.on("close", () => {
    // this is never called on nodemon restart, it is when ctrl+c is pressed
    console.log("Server close");
});



/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
    var addr = server.address();
    var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
    debug("Listening on " + bind);
}



process.on("SIGTERM", function() {
    // this is never called
    console.log("SIGTERM");        
    server.close(function() {
        process.exit(0);
    });
});
process.on("SIGINT", function() {
    // this is only called on ctrl+c, not restart
    console.log("SIGINT");
    server.close(function() {
        process.exit(0);
    });
});
process.on("SIGUSR2", function() {
    // never called
    console.log("SIGUSR2");
    server.close(function() {
        process.exit(0);
    });
});
process.on("beforeExit", () => {
    // nope, never called
    console.log("before exit");
});
process.on("exit", () => {
    // only called on ctrl+c, not on restart
    console.log("exit");
});

I am pretty sure at least one of those functions should handle the event and allow me to close the DB but if I start the server via nodemon /bin/www I just get the output related to the startup, if I then restart the server by typing rs, the output log looks like this:

Started directory watcher //normal startup
rs
[nodemon] starting `node ./bin/www` //none of the functions above is called
Lalonde answered 2/3, 2020 at 13:49 Comment(1)
I
1

You can configure nodemon to use another termination signal:

  • in console: nodemon --signal SIGTERM server.js
  • in nodemon config: "signal": "SIGTERM"

With SIGTERM for nodemon, my express app listens to SIGINT:

const shutdown = () => {
    console.debug('server: closing')
    server.close((err) => {
        if(err) {
            console.error('server: closed with ERROR', err)
            process.exit(81)
        }
        console.debug('server: closed')
        process.exit()
    })
}

process.on('SIGINT', () => {
    console.debug('process received SIGINT')
    shutdown()
})

process.on('SIGTERM', () => {
    console.debug('process received SIGTERM')
    shutdown()
})

Logs on restart:

[nodemon] restarting due to changes...
process received SIGINT
server: closing
server: closed
[nodemon] starting `node ./server.js`
Iodoform answered 1/1, 2023 at 19:34 Comment(0)
D
0

You can set event listeners for nodemon state changes. So, you can have it run a SQL query to kill all your app's processes, on restart.

I ran into the same problem and created a procedure in the database called kill_other_processes, and had nodemon call it on restart.

First, create a config file for nodemon in your root directory, called nodemon.json, with the following content:

{
  "events": {
    "restart": "mysql --user=username --password=password -Bse \"USE your_database; CALL kill_other_processes();\""
  }
}

Then, create the procedure kill_other_processes in your database (from this answer, or whichever other query you choose to end connections):

DROP PROCEDURE IF EXISTS kill_other_processes;

DELIMITER $$

CREATE PROCEDURE kill_other_processes()
BEGIN
  DECLARE finished INT DEFAULT 0;
  DECLARE proc_id INT;
  DECLARE proc_id_cursor CURSOR FOR SELECT id FROM information_schema.processlist;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;

  OPEN proc_id_cursor;
  proc_id_cursor_loop: LOOP
    FETCH proc_id_cursor INTO proc_id;

    IF finished = 1 THEN 
      LEAVE proc_id_cursor_loop;
    END IF;

    IF proc_id <> CONNECTION_ID() THEN
      KILL proc_id;
    END IF;

  END LOOP proc_id_cursor_loop;
  CLOSE proc_id_cursor;
END$$

DELIMITER ;

Now, nodemon will kill the connections on every restart.

Duvall answered 24/7, 2021 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.