Skip to content

Commit 5384d1e

Browse files
jacob-hdgergelyke
authored andcommitted
feat(onSendFailureDuringShutdown): enable to run an operation before sending the failure message
1 parent defa4aa commit 5384d1e

File tree

5 files changed

+116
-11
lines changed

5 files changed

+116
-11
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ const options = {
6060
// cleanup options
6161
timeout: 1000, // [optional = 1000] number of milliseconds before forcefull exiting
6262
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
63-
signals, // [optional = []] array of signals to listen for relative to shutdown
63+
signals, // [optional = []] array of signals to listen for relative to shutdown
6464
beforeShutdown, // [optional] called before the HTTP server starts its shutdown
6565
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm)
6666
onShutdown, // [optional] called right before exiting
67+
onSendFailureDuringShutdown, // [optional] called before sending each 503 during shutdowns
6768

6869
// both
6970
logger // [optional] logger function to be called with errors
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
const http = require('http')
3+
const server = http.createServer((req, res) => res.end('hello'))
4+
5+
const { createTerminus } = require('../../')
6+
7+
createTerminus(server, {
8+
healthChecks: {
9+
'/health': () => Promise.reject(new Error('failure'))
10+
},
11+
onSendFailureDuringShutdown: async () => {
12+
console.log('onSendFailureDuringShutdown')
13+
}
14+
})
15+
16+
server.listen(8000, () => {
17+
setTimeout(() => process.kill(process.pid, 'SIGTERM'), 600)
18+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict'
2+
const http = require('http')
3+
const server = http.createServer((req, res) => res.end('hello'))
4+
5+
const { createTerminus } = require('../../')
6+
7+
createTerminus(server, {
8+
healthChecks: {
9+
'/health': () => Promise.resolve()
10+
},
11+
onSendFailureDuringShutdown: async () => {
12+
console.log('onSendFailureDuringShutdown')
13+
},
14+
beforeShutdown: () => {
15+
return new Promise((resolve) => {
16+
setTimeout(resolve, 1000)
17+
})
18+
}
19+
})
20+
21+
server.listen(8000, () => {
22+
process.kill(process.pid, 'SIGTERM')
23+
})

lib/terminus.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ function noopResolves () {
1515
return Promise.resolve()
1616
}
1717

18-
function sendSuccess (res, info) {
18+
async function sendSuccess (res, options) {
19+
const { info } = options
20+
1921
res.statusCode = 200
2022
res.setHeader('Content-Type', 'application/json')
2123
if (info) {
@@ -27,7 +29,12 @@ function sendSuccess (res, info) {
2729
res.end(SUCCESS_RESPONSE)
2830
}
2931

30-
function sendFailure (res, error) {
32+
async function sendFailure (res, options) {
33+
const { error, onSendFailureDuringShutdown } = options
34+
35+
if (onSendFailureDuringShutdown) {
36+
await onSendFailureDuringShutdown()
37+
}
3138
res.statusCode = 503
3239
res.setHeader('Content-Type', 'application/json')
3340
if (error) {
@@ -46,23 +53,23 @@ const intialState = {
4653
function noop () {}
4754

4855
function decorateWithHealthCheck (server, state, options) {
49-
const { healthChecks, logger } = options
56+
const { healthChecks, logger, onSendFailureDuringShutdown } = options
5057

5158
server.listeners('request').forEach((listener) => {
5259
server.removeListener('request', listener)
5360
server.on('request', async (req, res) => {
5461
if (healthChecks[req.url]) {
5562
if (state.isShuttingDown) {
56-
return sendFailure(res)
63+
return sendFailure(res, { onSendFailureDuringShutdown })
5764
}
5865
let info
5966
try {
6067
info = await healthChecks[req.url]()
6168
} catch (error) {
6269
logger('healthcheck failed', error)
63-
return sendFailure(res, error.causes)
70+
return sendFailure(res, { error: error.causes })
6471
}
65-
sendSuccess(res, info)
72+
return sendSuccess(res, { info })
6673
} else {
6774
listener(req, res)
6875
}
@@ -103,6 +110,7 @@ function terminus (server, options = {}) {
103110
signals = [],
104111
timeout = 1000,
105112
healthChecks = {},
113+
onSendFailureDuringShutdown,
106114
onShutdown = noopResolves,
107115
beforeShutdown = noopResolves,
108116
logger = noop } = options
@@ -112,7 +120,8 @@ function terminus (server, options = {}) {
112120
if (Object.keys(healthChecks).length > 0) {
113121
decorateWithHealthCheck(server, state, {
114122
healthChecks,
115-
logger
123+
logger,
124+
onSendFailureDuringShutdown
116125
})
117126
}
118127

lib/terminus.spec.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,70 @@ describe('Terminus', () => {
136136
})
137137

138138
it('returns 503 once signal received', (done) => {
139-
execFile('node', ['lib/standalone-tests/terminus.onsignal.fail.js'])
139+
let responseAssertionsComplete = false
140+
141+
// We're only truly finished when the response has been analyzed and the forked http process has exited,
142+
// freeing up port 8000 for future tests
143+
execFile('node', ['lib/standalone-tests/terminus.onsignal.fail.js'], (error) => {
144+
expect(error.signal).to.eql('SIGINT')
145+
expect(responseAssertionsComplete).to.eql(true)
146+
done()
147+
})
148+
149+
// let the process start up
150+
setTimeout(() => {
151+
fetch('http://localhost:8000/health')
152+
.then(res => {
153+
expect(res.status).to.eql(503)
154+
responseAssertionsComplete = true
155+
})
156+
}, 300)
157+
})
158+
159+
it('calls onSendFailureDuringShutdown when sending 503 during shutdown', (done) => {
160+
let responseAssertionsComplete = false
161+
162+
// We're only truly finished when the response has been analyzed and the forked http process has exited,
163+
// freeing up port 8000 for future tests
164+
execFile('node', ['lib/standalone-tests/terminus.onsendfailureduringshutdown.js'],
165+
(error, stdout) => {
166+
expect(error.signal).to.eql('SIGTERM')
167+
expect(stdout).to.eql('onSendFailureDuringShutdown\n')
168+
expect(responseAssertionsComplete).to.eql(true)
169+
done()
170+
})
171+
172+
// let the process start up
173+
setTimeout(() => {
174+
fetch('http://localhost:8000/health')
175+
.then(res => {
176+
expect(res.status).to.eql(503)
177+
responseAssertionsComplete = true
178+
})
179+
}, 300)
180+
})
181+
182+
it('does NOT call onSendFailureDuringShutdown when sending 503 during failed healthcheck', (done) => {
183+
let responseAssertionsComplete = false
184+
185+
// We're only truly finished when the response has been analyzed and the forked http process has exited,
186+
// freeing up port 8000 for future tests
187+
execFile('node', ['lib/standalone-tests/terminus.onsendfailureduringshutdown.failed-health.js'],
188+
(error, stdout) => {
189+
expect(error.signal).to.eql('SIGTERM')
190+
// Here, we expect NOT to see "onSendFailureDuringShutdown"
191+
expect(stdout).to.eql('')
192+
expect(responseAssertionsComplete).to.eql(true)
193+
done()
194+
})
140195

141196
// let the process start up
142197
setTimeout(() => {
143198
fetch('http://localhost:8000/health')
144199
.then(res => {
145200
expect(res.status).to.eql(503)
146-
done()
201+
responseAssertionsComplete = true
147202
})
148-
.catch(done)
149203
}, 300)
150204
})
151205
})

0 commit comments

Comments
 (0)