JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q20196F
  • License MIT

All in one solution to gracefully shutdown your application through a simple delayed shutdown process. Optionally supports nodemon. (initialise, free, lock critical sections)

Package Exports

  • graceful-shutdown-manager

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (graceful-shutdown-manager) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

node-graceful-shutdown-manager

All in one solution to gracefully shutdown your application through a simple delayed shutdown process. Optionally supports nodemon. (initialise, free, lock critical sections)

Use for any node.js project which might be negatively impacted by immediate termination.

Locking is a mechanism which avoids beginning the shutdown process until a block of code has finished. Wrap important code such as file writing with lock() and unlock(). This will ensure no corruption occurs or resources are freed when they might be required. Ensure the code within the lock is synchronous. I recommend the fs-extra package for a drop-in fs promise replacement.

Nodemon (optional)

Used for auto reloading upon source file changes, useful for development. This assumes your entry point is the usual index.js and that your primary source code is somewhere inside the src directory.

Install

npm install -g nodemon

package.json

nodemon --signal SIGTERM -x 'node index.js'

  "scripts": {
    "development": "NODE_ENV=development nodemon --signal SIGTERM -x 'node index.js'"
  },
  "nodemonConfig": {
    "watch": [
      "./src"
    ],
    "ext": "js",
    "delay": "1"
  }

Template (example is below this template)

main.js

Program entry

/*
  GSM Functions:
    gsm.exit() Gracefully shutdown application
    gsm.free() Gracefully run the free events, without exiting. (autoreload code, etc)
    gsm.isExiting() Avoid continuing a loop if the application is waiting to exit
    gsm.isFreeing() Check if freeing

  Base Functions:
    base.lock() Stop the application from freeing if an object is locked
    base.unlock()
    base.isLocked()
*/

async function main() {
  global.gsm = require("graceful-shutdown-manager").Manager.create();

  try {
    global.database = require("./Database").create();

    // Start
    global.gsm.events.on("init", async () => {
      await global.database.init();
    });

    // Main
    global.gsm.events.on("main", async () => {
      // Run code after everything has been initialised
    });

    // Cleanup
    global.gsm.events.on("free", async () => {
      await global.database.free();
    });

    await global.gsm.init(); // Calls init, and then main, free will only be called when invoked
  } catch (e) {
    // All program errors end up here, even during the initialisation phase
    console.error(e);

    // await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
    global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
  }
}

main();

Database.js

const Base = require('graceful-shutdown-manager').Base;

class Database extends Base {
  constructor() {
    super();

    this.events.on("init", async () => {
      await this.connect();
    });

    this.events.on("free", async () => {
      await this.close();
    });

    // Remember to wrap any important code in locks, place them in your functions
    //this.lock();
    // Write to file or do something which cannot be interrupted
    //this.unlock();
  }

  connect() {
    return new Promise((resolve, reject)) => {
      // Connect and resolve, mysql uses callbacks
      resolve();
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      // End and resolve, mysql uses callbacks
      resolve();
    });
  }
}

module.exports = Database;

Example (mysql, express, locking)

main.js

/*
  GSM Functions:
    gsm.exit() Gracefully shutdown application
    gsm.free() Gracefully run the free events, without exiting. (autoreload code, etc)
    gsm.isExiting() Avoid continuing a loop if the application is waiting to exit
    gsm.isFreeing() Check if freeing

  Base Functions:
    base.lock() Stop the application from freeing if an object is locked
    base.unlock()
    base.isLocked()
*/

async function main() {
  global.gsm = require("graceful-shutdown-manager").Manager.create();

  try {
    global.database = require("./Database").create();
    global.express = require("./Express").create();
    global.app = require("./App").create();

    // Start
    global.gsm.events.on("init", async () => {
      await global.database.init();
      await global.express.init();
      await global.app.init();
    });

    // Main
    global.gsm.events.on("main", async () => {
      // Run code after everything has been initialised
    });

    // Cleanup
    global.gsm.events.on("free", async () => {
      await global.express.free();
      await global.database.free();
      await global.app.free();
    });

    await global.gsm.init(); // Calls init, and then main, free will only be called when invoked
  } catch (e) {
    // All program errors end up here, even during the initialisation phase
    console.error(e);

    // await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
    global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
  }
}

main();

Database.js

const Base = require('graceful-shutdown-manager').Base;
const Mysql = require('mysql');

const connection = Mysql.createConnection({
  host     : 'localhost',
  user     : 'user',
  password : 'pass',
  database : 'db'
});

class Database extends Base {
  constructor() {
    super();

    this.events.on("init", async () => {
      await this.connect();
    });

    this.events.on("free", async () => {
      await this.close();
    });
  }

  connect() {
    return new Promise((resolve, reject)) => {
      connection.connect((err) => {
        if (err) {
          console.error('error connecting: ' + err.stack);
          reject(err);
          return;
        }

        console.log('connected as id ' + connection.threadId);
        resolve();
      });
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      connection.end((err) => {
        if (err) {
          console.error('error: ' + err.stack);
          reject(err);
          return;
        }

        resolve();
      });
    });
  }
}

module.exports = Database;

Express.js

const Base = require('graceful-shutdown-manager').Base;
const express = require('express');
const app = express();

class Express extends Base {
  constructor() {
    super();

    // Initialise
    this.events.on("init", async () => {
      await this.listen();
    });

    // Free
    this.events.on("free", async () => {
      await this.close();
    });
  }

  listen() {
    return new Promise((resolve, reject)) => {
      app.listen(3000, () => {
        console.log(`Listening at http://localhost:3000`);
        resolve();
      })
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      app.close(() => {
        console.log(`Stopped listening`);
        resolve();
      })
    });
  }
}

module.exports = Express;

App.js

/*
  This file demonstrated the locking mechanism, which
  stops a program exiting while it's doing something important.

  An example would be writing to a file, processing a request, etc.
*/

const Base = require("graceful-shutdown-manager").Base;
const fs = require("fs").promises;

class App extends Base {
  constructor() {
    super();

    // Initialise
    this.events.on("init", async () => {
      this.logs = [];

      // Store logs to an array (for file saving), by overwriting the function
      this.oldConsoleLog = console.log;
      console.log = (...args) => {
        this.logs.push(args.join(" "));
        this.oldConsoleLog(...args);
      };

      // Save every minute
      this.saveInterval = setInterval(() => {
        this.saveLog();
      }, 1 * 60 * 1000);
    });

    // Free
    this.events.on("free", async () => {
      clearInterval(this.saveInterval);
      await this.saveLog();

      console.log = this.oldConsoleLog;
    });
  }

  async saveLog() {
    // This will cause the manager to wait until this object is unlocked before exiting, saving you a corrupted file
    this.lock();
    await fs.writeFile("log.txt", JSON.stringify(this.logs, NULL, 2));
    this.unlock();
  }
}

module.exports = App;