@superhero/core 4.0.0-beta.7 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/index.js +69 -54
- package/index.test.js +1 -1
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -44,10 +44,9 @@ await core.bootstrap()
|
|
|
44
44
|
|
|
45
45
|
// Locate a service
|
|
46
46
|
const myService = core.locate('myService')
|
|
47
|
-
console.log(myService)
|
|
48
47
|
|
|
49
48
|
// Graceful shutdown
|
|
50
|
-
await core.
|
|
49
|
+
await core.destroy()
|
|
51
50
|
```
|
|
52
51
|
|
|
53
52
|
### Clustering Example
|
|
@@ -66,8 +65,8 @@ await core.add('./path/to/config.js')
|
|
|
66
65
|
// Bootstrap core
|
|
67
66
|
await core.bootstrap()
|
|
68
67
|
|
|
69
|
-
//
|
|
70
|
-
await core.
|
|
68
|
+
// Destroy core when done
|
|
69
|
+
await core.destroy()
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
---
|
|
@@ -125,11 +124,11 @@ pass 4
|
|
|
125
124
|
---------------------------------------------------------------------------------------------------
|
|
126
125
|
file | line % | branch % | funcs % | uncovered lines
|
|
127
126
|
---------------------------------------------------------------------------------------------------
|
|
128
|
-
index.js | 74.
|
|
127
|
+
index.js | 74.21 | 67.03 | 74.51 | 19-24 97-100 121-123 131-136 139-141 185-190 201-2…
|
|
129
128
|
index.test.js | 100.00 | 100.00 | 100.00 |
|
|
130
129
|
worker.js | 100.00 | 100.00 | 100.00 |
|
|
131
130
|
---------------------------------------------------------------------------------------------------
|
|
132
|
-
all files | 77.
|
|
131
|
+
all files | 77.46 | 70.00 | 77.59 |
|
|
133
132
|
---------------------------------------------------------------------------------------------------
|
|
134
133
|
```
|
|
135
134
|
|
|
@@ -157,7 +156,7 @@ new Core(branch: string = undefined)
|
|
|
157
156
|
- **`cluster(forks: number, branch?: number, version?: number): Promise<number>`**
|
|
158
157
|
Start clustering with the specified number of workers.
|
|
159
158
|
|
|
160
|
-
- **`
|
|
159
|
+
- **`destroy(): Promise<void>`**
|
|
161
160
|
Gracefully shutdown the core, its workers, and services.
|
|
162
161
|
|
|
163
162
|
#### Properties
|
package/index.js
CHANGED
|
@@ -3,14 +3,25 @@ import cluster from 'node:cluster'
|
|
|
3
3
|
import EventEmitter from 'node:events'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import bootstrap from '@superhero/bootstrap'
|
|
6
|
-
import Config from '@superhero/config'
|
|
7
6
|
import Locate from '@superhero/locator'
|
|
8
7
|
import Log from '@superhero/log'
|
|
9
8
|
|
|
10
9
|
export default class Core
|
|
11
10
|
{
|
|
12
11
|
locate = new Locate
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
get config()
|
|
14
|
+
{
|
|
15
|
+
return this.locate.config
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set config(_)
|
|
19
|
+
{
|
|
20
|
+
const error = new Error('Can not set the config (read-only) property')
|
|
21
|
+
error.code = 'E_CORE_CONFIG_SET'
|
|
22
|
+
error.cause = 'Alter the config instance through the bundled locator'
|
|
23
|
+
throw error
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
/**
|
|
16
27
|
* @param {string} branch The branch to use to add branch specific configurations.
|
|
@@ -19,8 +30,7 @@ export default class Core
|
|
|
19
30
|
{
|
|
20
31
|
this.branch = branch
|
|
21
32
|
this.basePath = this.locate.pathResolver.basePath // synchronize the base path.
|
|
22
|
-
|
|
23
|
-
Core.#setupDestructor(this)
|
|
33
|
+
Core.#setupDestroyer(this)
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
#branch
|
|
@@ -56,69 +66,71 @@ export default class Core
|
|
|
56
66
|
static #cores = new Map
|
|
57
67
|
|
|
58
68
|
// Used to prevent multiple observers on the process events.
|
|
59
|
-
static #
|
|
69
|
+
static #destroyerIsSetup = false
|
|
60
70
|
|
|
61
|
-
// Used to prevent multiple
|
|
62
|
-
static #
|
|
71
|
+
// Used to prevent multiple destroy calls.
|
|
72
|
+
static #destroyerIsTriggered = false
|
|
63
73
|
|
|
64
|
-
static #
|
|
74
|
+
static #setupDestroyer(core)
|
|
65
75
|
{
|
|
66
76
|
Core.#cores.set(core, core)
|
|
67
77
|
|
|
68
|
-
if(false === Core.#
|
|
78
|
+
if(false === Core.#destroyerIsSetup)
|
|
69
79
|
{
|
|
70
|
-
Core.#
|
|
80
|
+
Core.#destroyerIsSetup = true
|
|
71
81
|
|
|
72
|
-
process.on('SIGINT', (signal) => Core.#
|
|
73
|
-
process.on('SIGTERM', (signal) => Core.#
|
|
82
|
+
process.on('SIGINT', (signal) => Core.#destroy(signal))
|
|
83
|
+
process.on('SIGTERM', (signal) => Core.#destroy(signal))
|
|
74
84
|
|
|
75
|
-
process.on('unhandledRejection', (reason) => Core.#
|
|
76
|
-
process.on('uncaughtException', (error) => Core.#
|
|
85
|
+
process.on('unhandledRejection', (reason) => Core.#destroy(false, reason))
|
|
86
|
+
process.on('uncaughtException', (error) => Core.#destroy(false, error))
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
/**
|
|
81
|
-
* Attempts to
|
|
91
|
+
* Attempts to destroy all core instances gracefully.
|
|
82
92
|
*/
|
|
83
|
-
static async #
|
|
93
|
+
static async #destroy(signal, reason)
|
|
84
94
|
{
|
|
85
|
-
if(Core.#
|
|
95
|
+
if(Core.#destroyerIsTriggered)
|
|
86
96
|
{
|
|
87
97
|
Core.log.info`redundant shutdown signal ${signal} waiting for previous shutdown process to finalize…`
|
|
88
98
|
reason && Core.log.fail`${reason}`
|
|
89
99
|
return
|
|
90
100
|
}
|
|
91
101
|
|
|
92
|
-
Core.#
|
|
102
|
+
Core.#destroyerIsTriggered = true
|
|
93
103
|
|
|
94
|
-
signal
|
|
104
|
+
signal
|
|
105
|
+
? Core.log.info`graceful shutdown initiated ${signal}`
|
|
106
|
+
: Core.log.info`graceful shutdown initiated`
|
|
95
107
|
|
|
96
108
|
const
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
destroyedCores = [],
|
|
110
|
+
destroyRejects = []
|
|
99
111
|
|
|
100
112
|
for(const core of Core.#cores.values())
|
|
101
113
|
{
|
|
102
|
-
|
|
114
|
+
destroyedCores.push((async () =>
|
|
103
115
|
{
|
|
104
116
|
try
|
|
105
117
|
{
|
|
106
|
-
await core.
|
|
118
|
+
await core.destroy()
|
|
107
119
|
}
|
|
108
120
|
catch(error)
|
|
109
121
|
{
|
|
110
|
-
|
|
122
|
+
destroyRejects.push(error)
|
|
111
123
|
}
|
|
112
124
|
})())
|
|
113
125
|
}
|
|
114
126
|
|
|
115
|
-
await Promise.all(
|
|
127
|
+
await Promise.all(destroyedCores)
|
|
116
128
|
|
|
117
|
-
if(
|
|
129
|
+
if(destroyRejects.length)
|
|
118
130
|
{
|
|
119
131
|
const error = new Error('Failed to shutdown gracefully')
|
|
120
|
-
error.code = '
|
|
121
|
-
error.cause =
|
|
132
|
+
error.code = 'E_CORE_DESTROY_GRACEFUL'
|
|
133
|
+
error.cause = destroyRejects
|
|
122
134
|
Core.log.fail`${error}`
|
|
123
135
|
setImmediate(() => process.exit(1))
|
|
124
136
|
}
|
|
@@ -133,25 +145,25 @@ export default class Core
|
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
|
|
136
|
-
async
|
|
148
|
+
async destroy()
|
|
137
149
|
{
|
|
138
150
|
// First remove the core instance from the static core registry
|
|
139
|
-
// to prevent multiple
|
|
151
|
+
// to prevent multiple destroy calls.
|
|
140
152
|
Core.#cores.delete(this)
|
|
141
153
|
|
|
142
|
-
// Then
|
|
154
|
+
// Then destroy the core workers, if clustered, using a timeout.
|
|
143
155
|
const
|
|
144
|
-
timeout
|
|
145
|
-
timeoutError
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
timeout = this.config.find('core/destroy/timeout', 15e3),
|
|
157
|
+
timeoutError = new Error(`Failed to destroy core within ${(timeout/1e3).toFixed(1)}s`),
|
|
158
|
+
destroyTimeout = (ctx) => new Promise((_, reject) => ctx.id = setTimeout(() => reject(timeoutError), timeout)),
|
|
159
|
+
destroyedWorkers = [],
|
|
160
|
+
destroyRejects = []
|
|
149
161
|
|
|
150
|
-
timeoutError.code = '
|
|
162
|
+
timeoutError.code = 'E_CORE_DESTROY_TIMEOUT'
|
|
151
163
|
|
|
152
164
|
for(const id in this.#workers)
|
|
153
165
|
{
|
|
154
|
-
|
|
166
|
+
destroyedWorkers.push((async () =>
|
|
155
167
|
{
|
|
156
168
|
// Attempt to kill the worker.
|
|
157
169
|
this.#workers[id].kill()
|
|
@@ -162,42 +174,42 @@ export default class Core
|
|
|
162
174
|
|
|
163
175
|
try
|
|
164
176
|
{
|
|
165
|
-
if(
|
|
177
|
+
if(destroyedWorkers.length)
|
|
166
178
|
{
|
|
167
179
|
const timeout = {}
|
|
168
|
-
await Promise.race([
|
|
180
|
+
await Promise.race([ destroyTimeout(timeout), Promise.all(destroyedWorkers) ])
|
|
169
181
|
clearTimeout(timeout.id)
|
|
170
182
|
}
|
|
171
183
|
}
|
|
172
184
|
catch(reason)
|
|
173
185
|
{
|
|
174
|
-
const error = new Error(`Failed to
|
|
175
|
-
error.code = '
|
|
186
|
+
const error = new Error(`Failed to destroy workers`)
|
|
187
|
+
error.code = 'E_CORE_DESTROY_WORKERS'
|
|
176
188
|
error.cause = reason
|
|
177
|
-
|
|
189
|
+
destroyRejects.push(error)
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
// Then
|
|
192
|
+
// Then destroy the core locator and all loaded services, restricted by a
|
|
181
193
|
// timeout if it takes to long.
|
|
182
194
|
try
|
|
183
195
|
{
|
|
184
196
|
const timeout = {}
|
|
185
|
-
await Promise.race([
|
|
197
|
+
await Promise.race([ destroyTimeout(timeout), this.locate.destroy() ])
|
|
186
198
|
clearTimeout(timeout.id)
|
|
187
199
|
}
|
|
188
200
|
catch(reason)
|
|
189
201
|
{
|
|
190
|
-
const error = new Error(`Failed to
|
|
191
|
-
error.code = '
|
|
202
|
+
const error = new Error(`Failed to destroy locator`)
|
|
203
|
+
error.code = 'E_CORE_DESTROY_LOCATOR'
|
|
192
204
|
error.cause = reason
|
|
193
|
-
|
|
205
|
+
destroyRejects.push(error)
|
|
194
206
|
}
|
|
195
207
|
|
|
196
|
-
if(
|
|
208
|
+
if(destroyRejects.length)
|
|
197
209
|
{
|
|
198
|
-
const error = new Error(`Failed to
|
|
199
|
-
error.code = '
|
|
200
|
-
error.cause =
|
|
210
|
+
const error = new Error(`Failed to destroy core gracefully`)
|
|
211
|
+
error.code = 'E_CORE_DESTROY'
|
|
212
|
+
error.cause = destroyRejects
|
|
201
213
|
throw error
|
|
202
214
|
}
|
|
203
215
|
}
|
|
@@ -212,8 +224,9 @@ export default class Core
|
|
|
212
224
|
|
|
213
225
|
// Forward the base path for the scoped path-resolver
|
|
214
226
|
// used by the locator and config services.
|
|
227
|
+
// OBS! this logic relies on that the locator and config
|
|
228
|
+
// services uses the same path-resolver.
|
|
215
229
|
this.locate.pathResolver.basePath = basePath
|
|
216
|
-
this.config.pathResolver.basePath = basePath
|
|
217
230
|
|
|
218
231
|
this.#eventlog.push({ type: 'basePath', basePath })
|
|
219
232
|
}
|
|
@@ -333,7 +346,7 @@ export default class Core
|
|
|
333
346
|
this.#workers[forkId].once('exit', this.#reloadWorker.bind(this, forkId, forkBranch, forkVersion))
|
|
334
347
|
this.#workers[forkId].sync = this.#createSynchoronizer(forkId)
|
|
335
348
|
|
|
336
|
-
Core.log.info
|
|
349
|
+
Core.log.info`${'CORE:' + forkId} ⇡ clustered`
|
|
337
350
|
|
|
338
351
|
try
|
|
339
352
|
{
|
|
@@ -346,6 +359,8 @@ export default class Core
|
|
|
346
359
|
error.cause = reason
|
|
347
360
|
throw error
|
|
348
361
|
}
|
|
362
|
+
|
|
363
|
+
Core.log.info`synchronized ${'CORE:' + forkId}`
|
|
349
364
|
}
|
|
350
365
|
|
|
351
366
|
return branch + forks
|
package/index.test.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superhero/core",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Core functionalities for the superhero framework.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
".": "./index.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@superhero/bootstrap": "^4.1.
|
|
13
|
-
"@superhero/
|
|
14
|
-
"@superhero/locator": "^4.1.2",
|
|
15
|
-
"@superhero/log": "^4.0.0"
|
|
12
|
+
"@superhero/bootstrap": "^4.1.1",
|
|
13
|
+
"@superhero/locator": "^4.2.2"
|
|
16
14
|
},
|
|
17
15
|
"scripts": {
|
|
18
16
|
"test": "node --trace-warnings --test --experimental-test-coverage"
|