Comparing version 4.2.0 to 4.3.0
@@ -8,2 +8,6 @@ "use strict"; | ||
var _events = _interopRequireDefault(require("events")); | ||
var _delay = _interopRequireDefault(require("delay")); | ||
var _express = _interopRequireDefault(require("express")); | ||
@@ -43,2 +47,4 @@ | ||
var _default = userConfiguration => { | ||
const eventEmitter = new _events.default(); | ||
const beacons = []; | ||
const shutdownHandlers = []; | ||
@@ -126,2 +132,21 @@ | ||
if (beacons.length) { | ||
yield new Promise(resolve => { | ||
const check = () => { | ||
if (beacons.length > 0) { | ||
log.info({ | ||
beacons | ||
}, 'program termination is on hold because there are live beacons'); | ||
} else { | ||
resolve(); | ||
} | ||
}; | ||
eventEmitter.on('beaconStateChange', () => { | ||
check(); | ||
}); | ||
check(); | ||
}); | ||
} | ||
for (var _i = 0, _shutdownHandlers = shutdownHandlers; _i < _shutdownHandlers.length; _i++) { | ||
@@ -195,3 +220,29 @@ const shutdownHandler = _shutdownHandlers[_i]; | ||
const createBeacon = context => { | ||
const beacon = { | ||
context: context || {} | ||
}; | ||
beacons.push(beacon); | ||
return { | ||
die: function () { | ||
var _die = _asyncToGenerator(function* () { | ||
log.trace({ | ||
beacon | ||
}, 'beacon has been killed'); | ||
beacons.splice(beacons.indexOf(beacon), 1); | ||
eventEmitter.emit('beaconStateChange'); | ||
yield (0, _delay.default)(0); | ||
}); | ||
function die() { | ||
return _die.apply(this, arguments); | ||
} | ||
return die; | ||
}() | ||
}; | ||
}; | ||
return { | ||
createBeacon, | ||
isServerReady: () => { | ||
@@ -198,0 +249,0 @@ return serverIsReady; |
@@ -87,3 +87,3 @@ { | ||
}, | ||
"version": "4.2.0" | ||
"version": "4.3.0" | ||
} |
@@ -10,2 +10,4 @@ <a name="lightship"></a> | ||
(Please read [Best practices](#best-practices) section.) | ||
Abstracts readiness/ liveness checks and graceful shutdown of Node.js services running in Kubernetes. | ||
@@ -24,2 +26,5 @@ | ||
* [Using with Express.js](#lightship-usage-examples-using-with-express-js) | ||
* [Beacons](#lightship-usage-examples-beacons) | ||
* [Best practices](#lightship-best-practices) | ||
* [Add a delay before stop handling incoming requests](#lightship-best-practices-add-a-delay-before-stop-handling-incoming-requests) | ||
* [FAQ](#lightship-faq) | ||
@@ -122,6 +127,7 @@ * [What is the reason for having separate `/live` and `/ready` endpoints?](#lightship-faq-what-is-the-reason-for-having-separate-live-and-ready-endpoints) | ||
type LightshipType = {| | ||
+server: http$Server, | ||
+createBeacon: (context?: BeaconContextType) => BeaconControllerType, | ||
+isServerReady: () => boolean, | ||
+isServerShuttingDown: () => boolean, | ||
+registerShutdownHandler: (shutdownHandler: ShutdownHandlerType) => void, | ||
+server: http$Server, | ||
+shutdown: () => Promise<void>, | ||
@@ -331,2 +337,92 @@ +signalNotReady: () => void, | ||
<a name="lightship-usage-examples-beacons"></a> | ||
### Beacons | ||
Beacons are used to delay the registered shutdown handler routine. | ||
A beacon can be created using `createBeacon()` method, e.g. | ||
```js | ||
const lightship = createLightship(); | ||
const beacon = lightship.createBeacon(); | ||
``` | ||
Beacon is live upon creation. Shutdown handlers are suspended until there are live beacons. | ||
To singnal that a beacon is dead, use `die()` method: | ||
```js | ||
beacon.die(); | ||
// This beacon is now dead. | ||
``` | ||
After beacon has been killed, it cannot be revived again. | ||
Use beacons to suspend the registered shutdown handler routine when you are processing a job queue, e.g. | ||
```js | ||
for (const job of jobs) { | ||
if (lightship.isServerShuttingDown()) { | ||
log.info('detected that the service is shutting down; terminating the event loop'); | ||
break; | ||
} | ||
const beacon = lightship.createBeacon(); | ||
// Do the job. | ||
await beacon.die(); | ||
} | ||
``` | ||
Additionally, you can provide beacons with context, e.g. | ||
```js | ||
for (const job of jobs) { | ||
if (lightship.isServerShuttingDown()) { | ||
log.info('detected that the service is shutting down; terminating the event loop'); | ||
break; | ||
} | ||
const beacon = lightship.createBeacon({ | ||
jobId: job.id | ||
}); | ||
// Do the job. | ||
await beacon.die(); | ||
} | ||
``` | ||
The logs will include messages describing the beacons that are holding the connection, e.g. | ||
```json | ||
{"context":{"package":"lightship","namespace":"factories/createLightship","logLevel":30,"beacons":[{"context":{"id":1}}]},"message":"program termination is on hold because there are live beacons","sequence":2,"time":1563892493825,"version":"1.0.0"} | ||
``` | ||
<a name="lightship-best-practices"></a> | ||
## Best practices | ||
<a name="lightship-best-practices-add-a-delay-before-stop-handling-incoming-requests"></a> | ||
### Add a delay before stop handling incoming requests | ||
It is important that you do not cease to handle new incoming requests immediatelly after receiving the shutdown signal. This is because there is a high probability of the SIGTERM signal being sent well before the iptables rules are updated on all nodes. The result is that the pod may still receive client requests after it has received the termination signal. If the app stops accepting connections immediately, it causes clients to receive "connection refused" types of errors. | ||
Properly shutting down an application includes these steps: | ||
1. Wait for a few seconds, then stop accepting new connections, | ||
2. Close all keep-alive connections that aren't in the middle of a request, | ||
3. Wait for all active requests to finish, and then | ||
4. Shut down completely. | ||
See [Handling Client Requests Properly with Kubernetes](https://freecontent.manning.com/handling-client-requests-properly-with-kubernetes/) for more information. | ||
<a name="lightship-faq"></a> | ||
@@ -333,0 +429,0 @@ ## FAQ |
// @flow | ||
// eslint-disable-next-line fp/no-events | ||
import EventEmitter from 'events'; | ||
import delay from 'delay'; | ||
import express from 'express'; | ||
@@ -38,2 +41,6 @@ import serializeError from 'serialize-error'; | ||
export default (userConfiguration?: UserConfigurationType): LightshipType => { | ||
const eventEmitter = new EventEmitter(); | ||
const beacons = []; | ||
const shutdownHandlers: Array<ShutdownHandlerType> = []; | ||
@@ -133,2 +140,22 @@ | ||
if (beacons.length) { | ||
await new Promise((resolve) => { | ||
const check = () => { | ||
if (beacons.length > 0) { | ||
log.info({ | ||
beacons | ||
}, 'program termination is on hold because there are live beacons'); | ||
} else { | ||
resolve(); | ||
} | ||
}; | ||
eventEmitter.on('beaconStateChange', () => { | ||
check(); | ||
}); | ||
check(); | ||
}); | ||
} | ||
for (const shutdownHandler of shutdownHandlers) { | ||
@@ -179,3 +206,26 @@ try { | ||
const createBeacon = (context) => { | ||
const beacon = { | ||
context: context || {} | ||
}; | ||
beacons.push(beacon); | ||
return { | ||
die: async () => { | ||
log.trace({ | ||
beacon | ||
}, 'beacon has been killed'); | ||
beacons.splice(beacons.indexOf(beacon), 1); | ||
eventEmitter.emit('beaconStateChange'); | ||
await delay(0); | ||
} | ||
}; | ||
}; | ||
return { | ||
createBeacon, | ||
isServerReady: () => { | ||
@@ -182,0 +232,0 @@ return serverIsReady; |
@@ -32,2 +32,9 @@ // @flow | ||
// eslint-disable-next-line flowtype/no-weak-types | ||
export type BeaconContextType = Object; | ||
export type BeaconControllerType = {| | ||
+die: () => Promise<void> | ||
|}; | ||
export opaque type StateType = | ||
@@ -46,6 +53,7 @@ 'SERVER_IS_NOT_READY' | | ||
export type LightshipType = {| | ||
+server: Server, | ||
+createBeacon: (context?: BeaconContextType) => BeaconControllerType, | ||
+isServerReady: () => boolean, | ||
+isServerShuttingDown: () => boolean, | ||
+registerShutdownHandler: (shutdownHandler: ShutdownHandlerType) => void, | ||
+server: Server, | ||
+shutdown: () => Promise<void>, | ||
@@ -52,0 +60,0 @@ +signalNotReady: () => void, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
60351
557
437