An open API service indexing awesome lists of open source software.

https://github.com/kanongil/exiting

Safely shutdown http://hapijs.com servers.
https://github.com/kanongil/exiting

hapi javascript node

Last synced: 7 months ago
JSON representation

Safely shutdown http://hapijs.com servers.

Awesome Lists containing this project

README

          

# Exiting

Safely shutdown [hapi.js](http://hapijs.com/) servers whenever the process exits.

[![Node.js CI](https://github.com/kanongil/exiting/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/kanongil/exiting/actions/workflows/ci.yaml)

## Details

While it is simple to start and stop a server, ensuring proper shutdown on external, or internal,
triggers can be cumbersome to handle properly.
**exiting** makes this easy by managing your Hapi servers, taking care of starting and stopping
them as appropriate.

Depending on the exit trigger, the hapi servers will either be gracefully stopped or aborted (by only
triggering `onPreStop` hooks).
The exit triggers are handled as detailed:

* Graceful exit with code `0`:
* `process.exit()` with exit code `0`.
* Unhandled `SIGINT` kill signal, through eg. `ctrl-c`.
* Unhandled `SIGTERM` kill signal.
* Unhandled `SIGQUIT` kill signal.
* Aborted exit:
* `process.exit()` with non-zero exit code.
* Unhandled `SIGHUP` kill signal (code `1`).
* Any uncaught exception (code `1`).
* Any unhandled rejection (code `1`).
* Any closed connection listeners, eg. on worker disconnect (code `255`).

If shutting down one of the servers is too slow, a timeout will eventually trigger an exit (exit code `255`).

The shutdown logic is programmed to handle almost any conceivable exit condition, and provides
100% test coverage.
The only instances that `onPreHook` code is not called, are uncatchable signals, like `SIGKILL`,
and fatal errors that trigger during shutdown.

## Example

Basic server example:

```js
const Hapi = require('hapi');
const Exiting = require('exiting');

const server = Hapi.Server();
const manager = Exiting.createManager(server);

server.events.on('stop', () => {

console.log('Server stopped.');
});

const provision = async () => {

server.route({
method: 'GET',
path: '/',
handler: () => 'Hello'
});

await manager.start();

console.log('Server started at:', server.info.uri);
};

provision();
```

The server and process life-cycle will now be managed by **exiting**.

If you need to delay the shutdown for processing, you can install an extention function on the
`onPreStop` or `onPostStop` extension points, eg:

```js
server.ext('onPreStop', () => {

return new Promise((resolve) => {

setTimeout(resolve, 1000);
});
});
```

Multiple servers example:

```js
const Hapi = require('hapi');
const Exiting = require('exiting');

const publicServer = Hapi.Server();
const adminServer = Hapi.Server();
const manager = Exiting.createManager([publicServer, adminServer]);

const provision = async () => {

publicServer.route({
method: 'GET',
path: '/',
handler: () => 'Hello'
});

adminServer.route({
method: 'GET',
path: '/',
handler: () => 'Hello Admin'
});

await manager.start();

console.log('Public server started at:', publicServer.info.uri);
console.log('Admin server started at:', adminServer.info.uri);
};

provision();
```

## Installation

Install using npm: `npm install exiting`.

## Usage

To enable **exiting** for you server, replace the call to `server.start()` with
`Exiting.createManager(server).start()`.

### Exiting.createManager(servers, [options])

Create a new exit manager for one or more hapi.js servers. The `options` object supports:

* `exitTimeout` - When exiting, force process exit after this amount of ms has elapsed. Default: `5000`.

### await manager.start()

Starts the manager and all the managed servers, as if `server.start()` is called on each server.
If any server fails to start, all will be stopped with `server.stop()` before the error is re-thrown.

Note that `process.exit()` is monkey patched to intercept such calls.
Starting also installs the signal handlers and an `uncaughtException` handler.

### await manager.stop([options])

Stops the manager and all the servers, as if `server.stop()` is called on each server.

## Notes on process.exit()

The `process.exit()` method is handled in a special manner that allows the asyncronous stop
logic to resolve before actually exiting. Since this can be called from anywhere in the code,
and subsequent code is never expected to be executed, the manager will throw an
`Exiting.ProcessExitError` to attempt to escape the current execution context. This allows
something like the following to still exit:

```js
while (true) {
process.exit(1);
}
```

This might not always work, and can potentially cause a lock up instead of exiting.
Eg. with this code:

```js
try {
process.exit(1);
}
catch (err) {
/* do nothing */
}
while (true) {}
```

You should avoid using `process.exit()` in your own code, and call `manager.stop()` instead.