10000 new FATAL state · vanioinformatika/node-appstate@1567155 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1567155

Browse files
committed
new FATAL state
1 parent 935e252 commit 1567155

File tree

5 files changed

+207
-22
lines changed

5 files changed

+207
-22
lines changed

README.md

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,20 @@ Changing application state.
4343
const appState = require('@vanioinformatika/appstate')()
4444
appState.init()
4545
appState.running()
46-
appState.error()
4746
appState.stopped()
47+
appState.error()
48+
appState.fatal()
49+
```
50+
51+
Checking application state (recommended).
52+
53+
```javascript
54+
const appState = require('@vanioinformatika/appstate')()
55+
appState.isInit()
56+
appState.isRunning()
57+
appState.isStopped()
58+
appState.isError()
59+
appState.isFatal()
4860
```
4961

5062
Reading application state.
@@ -54,17 +66,55 @@ const appState = require('@vanioinformatika/appstate')()
5466
let applicationState = appState.get()
5567
```
5668

57-
**Application state values are 'INIT', 'ERROR', 'RUNNING', 'STOPPED'**
69+
Listing state values.
5870

59-
## Best practice
71+
```javascript
72+
const appState = require('@vanioinformatika/appstate')()
73+
let applicationStateValues = appState.list()
74+
```
75+
76+
**Application state values are 'INIT', 'ERROR', 'RUNNING', 'STOPPED', 'FATAL'**
6077

61-
Using state:
78+
## Debug
6279

80+
Turn on debugging with env. variable: ```DEBUG=appState```
81+
82+
Debug messages are:
83+
84+
```javascript
85+
debug('info: appState has already set: ' + newAppState)
86+
debug('warn: invalid state changes from ' + appState + ' to ' + newAppState)
87+
debug('warn: unknow appState: ' + newAppState)
88+
```
89+
90+
## State machine
91+
92+
States:
93+
94+
* **INIT** - Default state, application is starting, initialization: starting phase (app doesn't handle request)
6395
* **RUNNING** - application is running
6496
* **STOPPED** - application is running, but programmatically stopped
65-
* **ERROR** - application is running, but has a critical error (e.g.: DB connection error): app doesn't serves requests
66-
* **INIT** - Default state, application is starting, initialization: starting phase (app doesn't handle request)
97+
* **ERROR** - application is running, but has a critical error (e.g.: DB connection error): app doesn't serve requests
98+
* **FATAL** - application doesn't serve request, and never comes to RUNNING state, all other state changes ignored
99+
100+
State machine:
101+
102+
* INIT icon:arrow-right[] [INIT, RUNNING, STOPPED, ERROR, FATAL]
103+
104+
* RUNNING icon:arrow-right[] [INIT, RUNNING, STOPPED, ERROR, FATAL]
105+
106+
* STOPPED icon:arrow-right[] [INIT, RUNNING, STOPPED, ERROR, FATAL]
107+
108+
* ERROR icon:arrow-right[] [INIT, RUNNING, STOPPED, ERROR, FATAL]
109+
110+
* FATAL icon:arrow-right[] [FATAL]
111+
112+
## Best practice
113+
114+
Turn on DEBUG on test environment and check debug messages.
115+
116+
Invalid state changes doesn't throw error, but ignored and logged.
67117

68-
Use a /health endpoint for load-balancers, and set to UP, if ```appState.get() === 'RUNNING'```, else DOWN.
118+
Use a /health endpoint for load-balancers, and set to UP, if ```appState.isRunning()```, else DOWN.
69119

70120
You can change anytime the application state, for example under initialization process: persistent DB connection error => appState.error()

appState.js

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const debug = require('debug')('appState')
12
/**
23
* Application state handler.
34
*/
@@ -19,18 +20,37 @@ module.exports = (cb) => {
1920
const state = {
2021
INIT: 'INIT',
2122
RUNNING: 'RUNNING',
23+
STOPPED: 'STOPPED',
2224
ERROR: 'ERROR',
23-
STOPPED: 'STOPPED'
25+
FATAL: 'FATAL'
26+
}
27+
const stateMachine = {
28+
INIT: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
29+
RUNNING: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
30+
STOPPED: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
31+
ERROR: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
32+
FATAL: [state.FATAL]
2433
}
2534
/**
2635
* Set application state to new state and loging it,
2736
* if state has changed.
2837
* @param String newAppState
2938
*/
3039
function changeAppState (newAppState) {
31-
if (appState !== newAppState) {
32-
if (cb) cb(appState, newAppState)
33-
appState = newAppState
40+
if (state[newAppState]) {
41+
if (stateMachine[appState].includes(newAppState)) {
42+
// valid state changes
43+
if (appState !== newAppState) {
44+
if (cb) cb(appState, newAppState)
45+
appState = newAppState
46+
} else {
47+
debug('info: appState has already set: ' + newAppState)
48+
}
49+
} else {
50+
debug('warn: invalid state changes from ' + appState + ' to ' + newAppState)
51+
}
52+
} else {
53+
debug('warn: unknow appState: ' + newAppState)
3454
}
3555
}
3656

@@ -47,7 +67,9 @@ module.exports = (cb) => {
4767
function stopped () {
4868
changeAppState(state.STOPPED)
4969
}
50-
70+
function fatal () {
71+
changeAppState(state.FATAL)
72+
}
5173
function isInit () {
5274
return appState === state.INIT
5375
}
@@ -60,6 +82,13 @@ module.exports = (cb) => {
6082
function isStopped () {
6183
return appState === state.STOPPED
6284
}
85+
function isFatal () {
86+
return appState === state.FATAL
87+
}
88+
89+
function getStateMachine () {
90+
return stateMachine
91+
}
6392

6493
function get () {
6594
return appState
@@ -68,5 +97,25 @@ module.exports = (cb) => {
6897
function list () {
6998
return state
7099
}
71-
return {init, running, error, stopped, get, list, isInit, isRunning, isError, isStopped}
100+
101+
function reset () {
102+
appState = state.INIT
103+
}
104+
105+
return {
106+
init,
107+
running,
108+
error,
109+
stopped,
110+
fatal,
111+
get,
112+
getStateMachine,
113+
list,
114+
isInit,
115+
isRunning,
116+
isError,
117+
isStopped,
118+
isFatal,
119+
reset
120+
}
72121
}

appState.spec.js

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const shouldSinon = require('should-sinon') // eslint-disable-line
66
describe('appState', () => {
77
it('handling undefined logger callback function', () => {
88
const appState = require('./appState')()
9+
appState.reset()
910
appState.get().should.equal('INIT')
1011
appState.running()
1112
appState.get().should.equal('RUNNING')
@@ -14,16 +15,19 @@ describe('appState', () => {
1415
it('handling logger callback function, logging changes', () => {
1516
const loggerCB = sinon.spy()
1617
const appState = require('./appState')(loggerCB)
18+
appState.reset()
1719
appState.init()
20+
appState.running()
1821
loggerCB.should.be.calledOnce()
1922
// appState hasn't changed: called one
20-
appState.init()
23+
appState.running()
2124
loggerCB.should.be.calledOnce()
2225
})
2326

2427
it('changed: logged', () => {
2528
const loggerCB = sinon.spy()
2629
const appState = require('./appState')(loggerCB)
30+
appState.reset()
2731
appState.running()
2832
loggerCB.should.be.calledOnce()
2933
appState.stopped()
@@ -36,68 +40,149 @@ describe('appState', () => {
3640
info (appState, newAppState) { console.log(`App state has changed from ${appState} to ${newAppState}`) }
3741
}
3842
const appState = require('./appState')(logger.info)
39-
appState.init()
43+
appState.reset()
44+
appState.running()
4045
console.log.should.be.calledOnce()
41-
console.log.should.be.calledWith('App state has changed from STOPPED to INIT')
46+
console.log.should.be.calledWith('App state has changed from INIT to RUNNING')
4247
console.log.restore()
4348
})
4449

4550
it('is INIT', () => {
4651
const appState = require('./appState')()
52+
appState.reset()
4753
appState.init()
4854
appState.get().should.be.equal('INIT')
4955
})
5056

5157
it('is RUNNING', () => {
5258
const appState = require('./appState')()
59+
appState.reset()
5360
appState.running()
5461
appState.get().should.be.equal('RUNNING')
5562
})
5663

5764
it('is STOPPED', () => {
5865
const appState = require('./appState')()
66+
appState.reset()
5967
appState.stopped()
6068
appState.get().should.be.equal('STOPPED')
6169
})
6270

6371
it('is ERROR', () => {
6472
const appState = require('./appState')()
73+
appState.reset()
6574
appState.error()
6675
appState.get().should.be.equal('ERROR')
6776
})
6877

6978
it('isInit ok', () => {
7079
const appState = require('./appState')()
80+
appState.reset()
7181
appState.init()
7282
appState.isInit().should.be.true()
7383
})
7484

7585
it('isRunning ok', () => {
7686
const appState = require('./appState')()
87+
appState.reset()
7788
appState.running()
7889
appState.isRunning().should.be.true()
7990
})
8091

8192
it('isStopped ok' 10000 , () => {
8293
const appState = require('./appState')()
94+
appState.reset()
8395
appState.stopped()
8496
appState.isStopped().should.be.true()
8597
})
8698

8799
it('isError ok', () => {
88100
const appState = require('./appState')()
101+
appState.reset()
89102
appState.error()
90103
appState.isError().should.be.true()
91104
})
92105

93106
it('list state values', () => {
94107
const appState = require('./appState')()
108+
appState.reset()
95109
appState.error()
96110
appState.list().should.be.eql({
97111
INIT: 'INIT',
98112
RUNNING: 'RUNNING',
113+
STOPPED: 'STOPPED',
99114
ERROR: 'ERROR',
100-
STOPPED: 'STOPPED'
115+
FATAL: 'FATAL'
101116
})
102117
})
118+
119+
it('get state machine values', () => {
120+
const appState = require('./appState')()
121+
const state = appState.list()
122+
appState.reset()
123+
appState.error()
124+
appState.getStateMachine().should.be.eql({
125+
INIT: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
126+
RUNNING: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
127+
STOPPED: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
128+
ERROR: [state.INIT, state.RUNNING, state.STOPPED, state.ERROR, state.FATAL],
129+
FATAL: [state.FATAL]
130+
})
131+
})
132+
133+
it('invalid state change FATAL to INIT', () => {
134+
const loggerCB = sinon.spy()
135+
const appState = require('./appState')(loggerCB)
136+
appState.reset()
137+
appState.get().should.equal('INIT')
138+
appState.running()
139+
loggerCB.should.be.calledOnce()
140+
appState.get().should.equal('RUNNING')
141+
appState.fatal()
142+
loggerCB.should.be.calledTwice()
143+
appState.init()
144+
loggerCB.should.be.calledTwice() // not called
145+
})
146+
147+
it('invalid state change FATAL to RUNNING', () => {
148+
const loggerCB = sinon.spy()
149+
const appState = require('./appState')(loggerCB)
150+
appState.reset()
151+
appState.get().should.equal('INIT')
152+
appState.running()
153+
loggerCB.should.be.calledOnce()
154+
appState.get().should.equal('RUNNING')
155+
appState.fatal()
156+
loggerCB.should.be.calledTwice()
157+
appState.running()
158+
loggerCB.should.be.calledTwice() // not called
159+
})
160+
161+
it('invalid state change FATAL to STOPPED', () => {
162+
const loggerCB = sinon.spy()
163+
const appState = require('./appState')(loggerCB)
164+
appState.reset()
165+
appState.get().should.equal('INIT')
166+
appState.running()
167+
loggerCB.should.be.calledOnce()
168+
appState.get().should.equal('RUNNING')
169+
appState.fatal()
170+
loggerCB.should.be.calledTwice()
171+
appState.stopped()
172+
loggerCB.should.be.calledTwice() // not called
173+
})
174+
175+
it('invalid state change FATAL to ERROR', () => {
176+
const loggerCB = sinon.spy()
177+
const appState = require('./appState')(loggerCB)
178+
appState.reset()
179+
appState.get().should.equal('INIT')
180+
appState.running()
181+
loggerCB.should.be.calledOnce()
182+
appState.get().should.equal('RUNNING')
183+
appState.fatal()
184+
loggerCB.should.be.calledTwice()
185+
appState.error()
186+
loggerCB.should.be.calledTwice() // not called
187+
})
103188
})

package-lock.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
0