@superhero/core 4.0.0-beta.6 → 4.0.0-beta.8
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 +4 -5
- package/index.js +64 -74
- package/index.test.js +1 -1
- package/package.json +3 -2
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
|
---
|
|
@@ -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
|
@@ -5,6 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import bootstrap from '@superhero/bootstrap'
|
|
6
6
|
import Config from '@superhero/config'
|
|
7
7
|
import Locate from '@superhero/locator'
|
|
8
|
+
import Log from '@superhero/log'
|
|
8
9
|
|
|
9
10
|
export default class Core
|
|
10
11
|
{
|
|
@@ -19,7 +20,7 @@ export default class Core
|
|
|
19
20
|
this.branch = branch
|
|
20
21
|
this.basePath = this.locate.pathResolver.basePath // synchronize the base path.
|
|
21
22
|
this.locate.set('@superhero/config', this.config)
|
|
22
|
-
Core.#
|
|
23
|
+
Core.#setupDestroyer(this)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
#branch
|
|
@@ -31,7 +32,7 @@ export default class Core
|
|
|
31
32
|
{
|
|
32
33
|
get: (target, prop, receiver) =>
|
|
33
34
|
{
|
|
34
|
-
// when pushing events to the eventlog, also
|
|
35
|
+
// when pushing events to the eventlog, also sync the event to all workers
|
|
35
36
|
if('push' === prop) return (...events) =>
|
|
36
37
|
{
|
|
37
38
|
setImmediate(() =>
|
|
@@ -48,79 +49,84 @@ export default class Core
|
|
|
48
49
|
}
|
|
49
50
|
})
|
|
50
51
|
|
|
52
|
+
// An exposed log instance taht the core instance uses that is open for modification.
|
|
53
|
+
static log = new Log({ label: process.env.CORE_CLUSTER_WORKER ? `[CORE:${process.env.CORE_CLUSTER_WORKER}]` : '[CORE]' })
|
|
54
|
+
|
|
51
55
|
// Used for graceful termination, if multiple cores are instanciated.
|
|
52
56
|
static #cores = new Map
|
|
53
57
|
|
|
54
58
|
// Used to prevent multiple observers on the process events.
|
|
55
|
-
static #
|
|
59
|
+
static #destroyerIsSetup = false
|
|
56
60
|
|
|
57
|
-
// Used to prevent multiple
|
|
58
|
-
static #
|
|
61
|
+
// Used to prevent multiple destroy calls.
|
|
62
|
+
static #destroyerIsTriggered = false
|
|
59
63
|
|
|
60
|
-
static #
|
|
64
|
+
static #setupDestroyer(core)
|
|
61
65
|
{
|
|
62
66
|
Core.#cores.set(core, core)
|
|
63
67
|
|
|
64
|
-
if(false === Core.#
|
|
68
|
+
if(false === Core.#destroyerIsSetup)
|
|
65
69
|
{
|
|
66
|
-
Core.#
|
|
70
|
+
Core.#destroyerIsSetup = true
|
|
67
71
|
|
|
68
|
-
process.on('SIGINT', (signal) => Core.#
|
|
69
|
-
process.on('SIGTERM', (signal) => Core.#
|
|
72
|
+
process.on('SIGINT', (signal) => Core.#destroy(signal))
|
|
73
|
+
process.on('SIGTERM', (signal) => Core.#destroy(signal))
|
|
70
74
|
|
|
71
|
-
process.on('unhandledRejection', (reason) => Core.#
|
|
72
|
-
process.on('uncaughtException', (error) => Core.#
|
|
75
|
+
process.on('unhandledRejection', (reason) => Core.#destroy(false, reason))
|
|
76
|
+
process.on('uncaughtException', (error) => Core.#destroy(false, error))
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/**
|
|
77
|
-
* Attempts to
|
|
81
|
+
* Attempts to destroy all core instances gracefully.
|
|
78
82
|
*/
|
|
79
|
-
static async #
|
|
83
|
+
static async #destroy(signal, reason)
|
|
80
84
|
{
|
|
81
|
-
if(Core.#
|
|
85
|
+
if(Core.#destroyerIsTriggered)
|
|
82
86
|
{
|
|
83
87
|
Core.log.info`redundant shutdown signal ${signal} waiting for previous shutdown process to finalize…`
|
|
84
|
-
reason && Core.log.
|
|
88
|
+
reason && Core.log.fail`${reason}`
|
|
85
89
|
return
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
Core.#
|
|
92
|
+
Core.#destroyerIsTriggered = true
|
|
89
93
|
|
|
90
|
-
signal
|
|
94
|
+
signal
|
|
95
|
+
? Core.log.info`graceful shutdown initiated ${signal}`
|
|
96
|
+
: Core.log.info`graceful shutdown initiated`
|
|
91
97
|
|
|
92
98
|
const
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
destroyedCores = [],
|
|
100
|
+
destroyRejects = []
|
|
95
101
|
|
|
96
102
|
for(const core of Core.#cores.values())
|
|
97
103
|
{
|
|
98
|
-
|
|
104
|
+
destroyedCores.push((async () =>
|
|
99
105
|
{
|
|
100
106
|
try
|
|
101
107
|
{
|
|
102
|
-
await core.
|
|
108
|
+
await core.destroy()
|
|
103
109
|
}
|
|
104
110
|
catch(error)
|
|
105
111
|
{
|
|
106
|
-
|
|
112
|
+
destroyRejects.push(error)
|
|
107
113
|
}
|
|
108
114
|
})())
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
await Promise.all(
|
|
117
|
+
await Promise.all(destroyedCores)
|
|
112
118
|
|
|
113
|
-
if(
|
|
119
|
+
if(destroyRejects.length)
|
|
114
120
|
{
|
|
115
121
|
const error = new Error('Failed to shutdown gracefully')
|
|
116
|
-
error.code = '
|
|
117
|
-
error.cause =
|
|
118
|
-
Core.log.error
|
|
122
|
+
error.code = 'E_CORE_DESTROY_GRACEFUL'
|
|
123
|
+
error.cause = destroyRejects
|
|
124
|
+
Core.log.fail`${error}`
|
|
119
125
|
setImmediate(() => process.exit(1))
|
|
120
126
|
}
|
|
121
127
|
else if(reason)
|
|
122
128
|
{
|
|
123
|
-
Core.log.
|
|
129
|
+
Core.log.fail`${reason}`
|
|
124
130
|
setImmediate(() => process.exit(1))
|
|
125
131
|
}
|
|
126
132
|
else
|
|
@@ -129,25 +135,25 @@ export default class Core
|
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
async
|
|
138
|
+
async destroy()
|
|
133
139
|
{
|
|
134
140
|
// First remove the core instance from the static core registry
|
|
135
|
-
// to prevent multiple
|
|
141
|
+
// to prevent multiple destroy calls.
|
|
136
142
|
Core.#cores.delete(this)
|
|
137
143
|
|
|
138
|
-
// Then
|
|
144
|
+
// Then destroy the core workers, if clustered, using a timeout.
|
|
139
145
|
const
|
|
140
|
-
timeout
|
|
141
|
-
timeoutError
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
timeout = this.config.find('core/destroy/timeout', 15e3),
|
|
147
|
+
timeoutError = new Error(`Failed to destroy core within ${(timeout/1e3).toFixed(1)}s`),
|
|
148
|
+
destroyTimeout = (ctx) => new Promise((_, reject) => ctx.id = setTimeout(() => reject(timeoutError), timeout)),
|
|
149
|
+
destroyedWorkers = [],
|
|
150
|
+
destroyRejects = []
|
|
145
151
|
|
|
146
|
-
timeoutError.code = '
|
|
152
|
+
timeoutError.code = 'E_CORE_DESTROY_TIMEOUT'
|
|
147
153
|
|
|
148
154
|
for(const id in this.#workers)
|
|
149
155
|
{
|
|
150
|
-
|
|
156
|
+
destroyedWorkers.push((async () =>
|
|
151
157
|
{
|
|
152
158
|
// Attempt to kill the worker.
|
|
153
159
|
this.#workers[id].kill()
|
|
@@ -158,42 +164,42 @@ export default class Core
|
|
|
158
164
|
|
|
159
165
|
try
|
|
160
166
|
{
|
|
161
|
-
if(
|
|
167
|
+
if(destroyedWorkers.length)
|
|
162
168
|
{
|
|
163
169
|
const timeout = {}
|
|
164
|
-
await Promise.race([
|
|
170
|
+
await Promise.race([ destroyTimeout(timeout), Promise.all(destroyedWorkers) ])
|
|
165
171
|
clearTimeout(timeout.id)
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
catch(reason)
|
|
169
175
|
{
|
|
170
|
-
const error = new Error(`Failed to
|
|
171
|
-
error.code = '
|
|
176
|
+
const error = new Error(`Failed to destroy workers`)
|
|
177
|
+
error.code = 'E_CORE_DESTROY_WORKERS'
|
|
172
178
|
error.cause = reason
|
|
173
|
-
|
|
179
|
+
destroyRejects.push(error)
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
// Then
|
|
182
|
+
// Then destroy the core locator and all loaded services, restricted by a
|
|
177
183
|
// timeout if it takes to long.
|
|
178
184
|
try
|
|
179
185
|
{
|
|
180
186
|
const timeout = {}
|
|
181
|
-
await Promise.race([
|
|
187
|
+
await Promise.race([ destroyTimeout(timeout), this.locate.destroy() ])
|
|
182
188
|
clearTimeout(timeout.id)
|
|
183
189
|
}
|
|
184
190
|
catch(reason)
|
|
185
191
|
{
|
|
186
|
-
const error = new Error(`Failed to
|
|
187
|
-
error.code = '
|
|
192
|
+
const error = new Error(`Failed to destroy locator`)
|
|
193
|
+
error.code = 'E_CORE_DESTROY_LOCATOR'
|
|
188
194
|
error.cause = reason
|
|
189
|
-
|
|
195
|
+
destroyRejects.push(error)
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
if(
|
|
198
|
+
if(destroyRejects.length)
|
|
193
199
|
{
|
|
194
|
-
const error = new Error(`Failed to
|
|
195
|
-
error.code = '
|
|
196
|
-
error.cause =
|
|
200
|
+
const error = new Error(`Failed to destroy core gracefully`)
|
|
201
|
+
error.code = 'E_CORE_DESTROY'
|
|
202
|
+
error.cause = destroyRejects
|
|
197
203
|
throw error
|
|
198
204
|
}
|
|
199
205
|
}
|
|
@@ -329,7 +335,7 @@ export default class Core
|
|
|
329
335
|
this.#workers[forkId].once('exit', this.#reloadWorker.bind(this, forkId, forkBranch, forkVersion))
|
|
330
336
|
this.#workers[forkId].sync = this.#createSynchoronizer(forkId)
|
|
331
337
|
|
|
332
|
-
Core.log.info
|
|
338
|
+
Core.log.info`${'CORE:' + forkId} ⇡ clustered`
|
|
333
339
|
|
|
334
340
|
try
|
|
335
341
|
{
|
|
@@ -342,6 +348,8 @@ export default class Core
|
|
|
342
348
|
error.cause = reason
|
|
343
349
|
throw error
|
|
344
350
|
}
|
|
351
|
+
|
|
352
|
+
Core.log.info`synchronized ${'CORE:' + forkId}`
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
return branch + forks
|
|
@@ -377,7 +385,7 @@ export default class Core
|
|
|
377
385
|
|
|
378
386
|
if('string' === typeof absolutePath)
|
|
379
387
|
{
|
|
380
|
-
serviceMap[entry] = path.normalize(absolutePath
|
|
388
|
+
serviceMap[entry] = path.normalize(path.join(absolutePath, servicePath))
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
}
|
|
@@ -634,7 +642,7 @@ export default class Core
|
|
|
634
642
|
|
|
635
643
|
if('string' === typeof absolutePath)
|
|
636
644
|
{
|
|
637
|
-
dependencies[i] = path.normalize(absolutePath
|
|
645
|
+
dependencies[i] = path.normalize(path.join(absolutePath, dependency))
|
|
638
646
|
}
|
|
639
647
|
}
|
|
640
648
|
}
|
|
@@ -734,22 +742,4 @@ export default class Core
|
|
|
734
742
|
throw error
|
|
735
743
|
}
|
|
736
744
|
}
|
|
737
|
-
|
|
738
|
-
// Will make the log methods availible to overwrite
|
|
739
|
-
// if a custom logging is desired.
|
|
740
|
-
static log =
|
|
741
|
-
{
|
|
742
|
-
label : process.env.CORE_CLUSTER_WORKER
|
|
743
|
-
? `[CORE:${process.env.CORE_CLUSTER_WORKER}]`
|
|
744
|
-
: '[CORE]',
|
|
745
|
-
icon : ' ⇢ ',
|
|
746
|
-
basic : (template, ...args) => template.reduce((result, part, i) => result + args[i - 1] + part),
|
|
747
|
-
simple : (template, ...args) => this.log.label + this.log.icon + this.log.basic(template, ...args),
|
|
748
|
-
colors : (template, ...args) => '\x1b[90m' + this.log.supress`${this.log.label + this.log.icon}` + template.reduce((result, part, i) => result + '\x1b[96m\x1b[2m' + args[i - 1] + '\x1b[90m' + part) + '\x1b[0m',
|
|
749
|
-
supress : (template, ...args) => '\x1b[1m\x1b[2m' + this.log.basic(template, ...args) + '\x1b[22m',
|
|
750
|
-
format : (...args) => this.log.colors(...args),
|
|
751
|
-
info : (...args) => console.info (this.log.format(...args)),
|
|
752
|
-
warning : (...args) => console.warn (this.log.format(...args)),
|
|
753
|
-
error : (...args) => console.error (this.log.format`failure`, ...args)
|
|
754
|
-
}
|
|
755
745
|
}
|
package/index.test.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superhero/core",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.8",
|
|
4
4
|
"description": "Core functionalities for the superhero framework.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@superhero/bootstrap": "^4.1.0",
|
|
13
13
|
"@superhero/config": "^4.1.2",
|
|
14
|
-
"@superhero/locator": "^4.
|
|
14
|
+
"@superhero/locator": "^4.2.0",
|
|
15
|
+
"@superhero/log": "^4.0.0"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
18
|
"test": "node --trace-warnings --test --experimental-test-coverage"
|