diff --git a/src/lib/app.ts b/src/lib/app.ts index cecf25c4c4..07b8e7b096 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -1,7 +1,7 @@ import { publicFolder } from 'unleash-frontend'; import fs from 'fs'; import EventEmitter from 'events'; -import express from 'express'; +import express, { Application } from 'express'; import cors from 'cors'; import compression from 'compression'; import favicon from 'serve-favicon'; @@ -30,7 +30,7 @@ export default function getApp( stores: IUnleashStores, services: IUnleashServices, eventBus?: EventEmitter, -): any { +): Application { const app = express(); const baseUriPath = config.server.baseUriPath || ''; diff --git a/src/lib/server-impl.test.js b/src/lib/server-impl.test.js index a6899718c9..31c51280b5 100644 --- a/src/lib/server-impl.test.js +++ b/src/lib/server-impl.test.js @@ -39,7 +39,7 @@ jest.mock('./metrics', () => ({ jest.mock('./db', () => ({ createStores() { return { - db: { destroy: cb => cb() }, + db: { destroy: () => undefined }, clientInstanceStore: { destroy: noop }, clientMetricsStore: { destroy: noop, on: noop }, eventStore, diff --git a/src/lib/server-impl.ts b/src/lib/server-impl.ts index 3ae964b483..f9e03c0e53 100644 --- a/src/lib/server-impl.ts +++ b/src/lib/server-impl.ts @@ -1,12 +1,13 @@ 'use strict'; import EventEmitter from 'events'; +import { Server } from 'http'; import { IUnleash } from './types/core'; import { IUnleashConfig, IUnleashOptions } from './types/option'; import version from './util/version'; import migrator from '../migrator'; import getApp from './app'; -import { createMetricsMonitor } from './metrics'; +import MetricsMonitor, { createMetricsMonitor } from './metrics'; import { createStores } from './db'; import { createServices } from './services'; import { createConfig } from './create-config'; @@ -16,10 +17,13 @@ import * as permissions from './types/permissions'; import AuthenticationRequired from './types/authentication-required'; import * as eventType from './types/events'; import { addEventHook } from './event-hook'; +import registerGracefulShutdown from './util/graceful-shutdown'; +import { IUnleashStores } from './types/stores'; -async function closeServer(opts): Promise { - const { server, metricsMonitor } = opts; - +async function closeServer( + server: Server, + metricsMonitor: MetricsMonitor, +): Promise { metricsMonitor.stopMonitoring(); return new Promise((resolve, reject) => { @@ -27,15 +31,11 @@ async function closeServer(opts): Promise { }); } -async function destroyDatabase(stores): Promise { +async function destroyDatabase(stores: IUnleashStores): Promise { const { db, clientInstanceStore, clientMetricsStore } = stores; - - return new Promise((resolve, reject) => { - clientInstanceStore.destroy(); - clientMetricsStore.destroy(); - - db.destroy(error => (error ? reject(error) : resolve())); - }); + clientInstanceStore.destroy(); + clientMetricsStore.destroy(); + return db.destroy(); } async function createApp( @@ -83,12 +83,11 @@ async function createApp( const server = app.listen(config.listen, () => logger.info('Unleash has started.', server.address()), ); - const stop = () => { + const stop = async () => { logger.info('Shutting down Unleash...'); - return closeServer({ server, metricsMonitor }).then(() => - destroyDatabase(stores), - ); + await closeServer(server, metricsMonitor); + return destroyDatabase(stores); }; server.keepAliveTimeout = config.server.keepAliveTimeout; @@ -98,7 +97,7 @@ async function createApp( }); server.on('error', reject); } else { - const stop = () => { + const stop = async () => { logger.info('Shutting down Unleash...'); metricsMonitor.stopMonitoring(); return destroyDatabase(stores); @@ -125,7 +124,10 @@ async function start(opts: IUnleashOptions = {}): Promise { throw err; } - return createApp(config, true); + const unleash = await createApp(config, true); + logger.info('register graceful shutdown'); + registerGracefulShutdown(unleash, logger); + return unleash; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types diff --git a/src/lib/types/core.ts b/src/lib/types/core.ts index bd7aa83b63..70d1b07697 100644 --- a/src/lib/types/core.ts +++ b/src/lib/types/core.ts @@ -18,6 +18,6 @@ export interface IUnleash { stores: IUnleashStores; server?: http.Server | https.Server; services: IUnleashServices; - stop: () => void; + stop: () => Promise; version: string; } diff --git a/src/lib/util/graceful-shutdown.ts b/src/lib/util/graceful-shutdown.ts new file mode 100644 index 0000000000..eaa5f13841 --- /dev/null +++ b/src/lib/util/graceful-shutdown.ts @@ -0,0 +1,28 @@ +import { Logger } from '../logger'; +import { IUnleash } from '../types/core'; + +function registerGracefulShutdown(unleash: IUnleash, logger: Logger): void { + process.on('SIGINT', async () => { + try { + logger.info('Graceful shutdown signal (SIGINT) received.'); + await unleash.stop(); + process.exit(0); + } catch (e) { + logger.error('Unable to shutdown Unleash. Hard exit!', e); + process.exit(1); + } + }); + + process.on('SIGTERM', async () => { + try { + logger.info('Graceful shutdown signal (SIGTERM) received.'); + await unleash.stop(); + process.exit(0); + } catch (e) { + logger.error('Unable to shutdown Unleash. Hard exit!', e); + process.exit(1); + } + }); +} + +export default registerGracefulShutdown; diff --git a/websitev2/docs/deploy/configuring-unleash.md b/websitev2/docs/deploy/configuring-unleash.md index 6f8c2f3e56..cda235f4bf 100644 --- a/websitev2/docs/deploy/configuring-unleash.md +++ b/websitev2/docs/deploy/configuring-unleash.md @@ -5,9 +5,9 @@ title: Configuring Unleash > This is the guide on how to configure **Unleash v4 self-hosted**. If you are still using Unleash v3 you should checkout [configuring Unleash v3](./configuring_unleash_v3) -# Must configure +## Must configure -## Database details {#database-details} +### Database details {#database-details} In order for Unleash server to work, you must setup database connection details. @@ -22,7 +22,7 @@ In order for Unleash server to work, you must setup database connection details. - We also support `DATABASE_URL` see [libpq's doc](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) for full format explanation. In short: `postgres://USER:PASSWORD@HOST:PORT/DATABASE` - If you're using secret files from kubernetes and would like to load a `DATABASE_URL` format from a file, use `DATABASE_URL_FILE` and point it to a path containing a connection URL. -# Nice to configure +## Nice to configure ### Unleash URL {#unleash-url} @@ -38,7 +38,7 @@ Used to send reset-password mails and welcome mails when onboarding new users. < For [more details, see here](./email.md) -# Further customization +## Further customization In order to customize "anything" in Unleash you need to use [Unleash from Node.js](./getting_started#option-two---from-nodejs) or start the [docker image](./getting_started#option-one---use-docker) with environment variables. @@ -120,18 +120,44 @@ unleash.start(unleashOptions); If you're using Unleash as part of a larger express app, you can disable the automatic server start by calling `server.create`. It takes the same options as `server.start`, but will not begin listening for connections. ```js +const unleash = require('express'); const unleash = require('unleash-server'); -// ... const app = express(); +const app = express(); -unleash - .create({ +const start = async () => { + const instance = await unleash.create({ + databaseUrl: 'postgres://unleash_user:password@localhost:5432/unleash', + }); + app.use(instance.app); + console.log(`Unleash app generated and attached to parent application`); +}; + +start(); +``` + +### Graceful shutdown {#shutdown-unleash} + +> PS! Unleash will listen for the `SIGINT` signal and automatically trigger graceful shutdown of Unleash. + +If you need to stop Unleash (close database connections, and stop running Unleash tasks) you may use the stop function. Be aware that it is not possible to restart the Unleash instance after stopping it, but you can create a new instance of Unleash. + +```js +const express = require('express'); +const unleash = require('unleash-server'); +const app = express(); + +const start = async () => { + const instance = await unleash.start({ databaseUrl: 'postgres://unleash_user:password@localhost:5432/unleash', port: 4242, - }) - .then(result => { - app.use(result.app); - console.log(`Unleash app generated and attached to parent application`); }); + + //Sometime later + await instance.stop(); + console.log('Unleash has now stopped'); +}; + +start(); ``` ## Securing Unleash {#securing-unleash}