@platformatic/runtime 1.35.5 → 1.36.0
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/fixtures/botched-start/platformatic.runtime.json +14 -0
- package/fixtures/botched-start/services/a/platformatic.service.json +14 -0
- package/fixtures/botched-start/services/a/plugin.js +11 -0
- package/fixtures/restart-on-crash/platformatic.runtime.json +14 -0
- package/fixtures/restart-on-crash/services/a/platformatic.service.json +14 -0
- package/fixtures/restart-on-crash/services/a/plugin.js +9 -0
- package/lib/api-client.js +20 -1
- package/lib/errors.js +2 -0
- package/lib/start.js +71 -57
- package/lib/worker.js +4 -4
- package/package.json +14 -13
package/lib/api-client.js
CHANGED
|
@@ -28,15 +28,32 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
28
28
|
super()
|
|
29
29
|
this.setMaxListeners(MAX_LISTENERS_COUNT)
|
|
30
30
|
|
|
31
|
-
this.worker = worker
|
|
32
31
|
this.#configManager = configManager
|
|
33
32
|
this.#runtimeTmpDir = getRuntimeTmpDir(configManager.dirname)
|
|
33
|
+
|
|
34
|
+
this.setWorker(worker)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async setWorker (worker) {
|
|
38
|
+
if (!worker) {
|
|
39
|
+
throw new errors.WorkerIsRequired()
|
|
40
|
+
}
|
|
41
|
+
if (this.worker) {
|
|
42
|
+
this.worker.removeAllListeners('message')
|
|
43
|
+
// Ignore all things that happen after the worker has been replaced
|
|
44
|
+
this.#exitPromise.catch(() => {})
|
|
45
|
+
}
|
|
46
|
+
this.worker = worker
|
|
34
47
|
this.#exitPromise = this.#exitHandler()
|
|
35
48
|
this.worker.on('message', (message) => {
|
|
36
49
|
if (message.operationId) {
|
|
37
50
|
this.emit(message.operationId, message)
|
|
38
51
|
}
|
|
39
52
|
})
|
|
53
|
+
|
|
54
|
+
if (this.started) {
|
|
55
|
+
await this.start()
|
|
56
|
+
}
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
async #getRuntimePackageJson () {
|
|
@@ -71,12 +88,14 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
async start () {
|
|
91
|
+
this.started = true
|
|
74
92
|
const address = await this.#sendCommand('plt:start-services')
|
|
75
93
|
this.emit('start', address)
|
|
76
94
|
return address
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
async close () {
|
|
98
|
+
this.started = false
|
|
80
99
|
await this.#sendCommand('plt:stop-services')
|
|
81
100
|
|
|
82
101
|
this.worker.postMessage({ command: 'plt:close' })
|
package/lib/errors.js
CHANGED
|
@@ -25,6 +25,8 @@ module.exports = {
|
|
|
25
25
|
FailedToUnlinkManagementApiSocket: createError(`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`, 'Failed to unlink management API socket "%s"'),
|
|
26
26
|
LogFileNotFound: createError(`${ERROR_PREFIX}_LOG_FILE_NOT_FOUND`, 'Log file with index %s not found', 404),
|
|
27
27
|
CannotFindGeneratorForTemplateError: createError(`${ERROR_PREFIX}_CANNOT_FIND_GENERATOR_FOR_TEMPLATE`, 'Cannot find a generator for template "%s"'),
|
|
28
|
+
WorkerExitCodeError: createError(`${ERROR_PREFIX}_WORKER_EXIT_CODE`, 'The worker exited with non-zero exit code "%s"'),
|
|
29
|
+
WorkerIsRequired: createError(`${ERROR_PREFIX}_REQUIRED_WORKER`, 'The worker parameter is required'),
|
|
28
30
|
|
|
29
31
|
// TODO: should remove next one as it's not used anymore
|
|
30
32
|
CannotRemoveServiceOnUpdateError: createError(`${ERROR_PREFIX}_CANNOT_REMOVE_SERVICE_ON_UPDATE`, 'Cannot remove service "%s" when updating a Runtime')
|
package/lib/start.js
CHANGED
|
@@ -4,6 +4,7 @@ const { once } = require('node:events')
|
|
|
4
4
|
const inspector = require('node:inspector')
|
|
5
5
|
const { join, resolve, dirname } = require('node:path')
|
|
6
6
|
const { writeFile } = require('node:fs/promises')
|
|
7
|
+
const { setTimeout: sleep } = require('node:timers/promises')
|
|
7
8
|
const { pathToFileURL } = require('node:url')
|
|
8
9
|
const { Worker } = require('node:worker_threads')
|
|
9
10
|
const { start: serviceStart } = require('@platformatic/service')
|
|
@@ -12,6 +13,7 @@ const closeWithGrace = require('close-with-grace')
|
|
|
12
13
|
const { loadConfig } = require('./load-config')
|
|
13
14
|
const { startManagementApi } = require('./management-api')
|
|
14
15
|
const { startPrometheusServer } = require('./prom-server.js')
|
|
16
|
+
const { WorkerExitCodeError } = require('./errors')
|
|
15
17
|
const { parseInspectorOptions, wrapConfigInRuntimeConfig } = require('./config')
|
|
16
18
|
const { RuntimeApiClient, getRuntimeLogsDir } = require('./api-client.js')
|
|
17
19
|
const errors = require('./errors')
|
|
@@ -25,6 +27,18 @@ const kWorkerExecArgv = [
|
|
|
25
27
|
kLoaderFile
|
|
26
28
|
]
|
|
27
29
|
|
|
30
|
+
function startWorker ({ config, dirname, runtimeLogsDir }, env) {
|
|
31
|
+
const worker = new Worker(kWorkerFile, {
|
|
32
|
+
/* c8 ignore next */
|
|
33
|
+
execArgv: config.hotReload ? kWorkerExecArgv : [],
|
|
34
|
+
transferList: config.loggingPort ? [config.loggingPort] : [],
|
|
35
|
+
workerData: { config, dirname, runtimeLogsDir },
|
|
36
|
+
env
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return worker
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
async function buildRuntime (configManager, env = process.env) {
|
|
29
43
|
const config = configManager.current
|
|
30
44
|
|
|
@@ -46,36 +60,21 @@ async function buildRuntime (configManager, env = process.env) {
|
|
|
46
60
|
// The configManager cannot be transferred to the worker, so remove it.
|
|
47
61
|
delete config.configManager
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
/* c8 ignore next */
|
|
51
|
-
execArgv: config.hotReload ? kWorkerExecArgv : [],
|
|
52
|
-
transferList: config.loggingPort ? [config.loggingPort] : [],
|
|
53
|
-
workerData: { config, dirname, runtimeLogsDir },
|
|
54
|
-
env
|
|
55
|
-
})
|
|
63
|
+
let worker = startWorker({ config, dirname, runtimeLogsDir }, env)
|
|
56
64
|
|
|
57
65
|
let managementApi = null
|
|
58
66
|
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
worker.on('error', () => {
|
|
73
|
-
// If this is the only 'error' handler, then exit the process as the default
|
|
74
|
-
// behavior. If anything else is listening for errors, then don't exit.
|
|
75
|
-
if (worker.listenerCount('error') === 1) {
|
|
76
|
-
// The error is logged in the worker.
|
|
77
|
-
process.exit(1)
|
|
78
|
-
}
|
|
67
|
+
let exiting = false
|
|
68
|
+
closeWithGrace((event, cb) => {
|
|
69
|
+
exiting = true
|
|
70
|
+
worker.postMessage(event)
|
|
71
|
+
worker.once('exit', (code) => {
|
|
72
|
+
if (code !== 0) {
|
|
73
|
+
cb(new WorkerExitCodeError(code))
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
cb()
|
|
77
|
+
})
|
|
79
78
|
})
|
|
80
79
|
|
|
81
80
|
if (config.hotReload) {
|
|
@@ -84,22 +83,34 @@ async function buildRuntime (configManager, env = process.env) {
|
|
|
84
83
|
worker.postMessage({ signal: 'SIGUSR2' })
|
|
85
84
|
})
|
|
86
85
|
|
|
87
|
-
// TODO(mcollina): refactor to not alter globals here
|
|
88
|
-
closeWithGrace((event, cb) => {
|
|
89
|
-
if (isWorkerAlive) {
|
|
90
|
-
worker.postMessage(event)
|
|
91
|
-
exited = cb
|
|
92
|
-
} else {
|
|
93
|
-
setImmediate(cb)
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
86
|
/* c8 ignore next 3 */
|
|
98
87
|
configManager.on('update', () => {
|
|
99
88
|
// TODO(cjihrig): Need to clean up and restart the worker.
|
|
100
89
|
})
|
|
101
90
|
}
|
|
102
91
|
|
|
92
|
+
function setupExit () {
|
|
93
|
+
worker.on('exit', (code) => {
|
|
94
|
+
// runtimeApiClient.started can be false if a stop command was issued
|
|
95
|
+
// via the management API.
|
|
96
|
+
if (exiting || !runtimeApiClient.started) {
|
|
97
|
+
// We must stop those here in case the `closeWithGrace` callback
|
|
98
|
+
// was not called.
|
|
99
|
+
configManager.fileWatcher?.stopWatching()
|
|
100
|
+
managementApi?.close()
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
worker = startWorker({ config, dirname, runtimeLogsDir }, env)
|
|
105
|
+
setupExit()
|
|
106
|
+
once(worker, 'message').then(() => {
|
|
107
|
+
runtimeApiClient.setWorker(worker)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setupExit()
|
|
113
|
+
|
|
103
114
|
await once(worker, 'message') // plt:init
|
|
104
115
|
|
|
105
116
|
const runtimeApiClient = new RuntimeApiClient(
|
|
@@ -176,32 +187,35 @@ async function startCommand (args) {
|
|
|
176
187
|
await writeFile(toWrite, JSON.stringify(config, null, 2))
|
|
177
188
|
return startCommand(['--config', toWrite])
|
|
178
189
|
}
|
|
179
|
-
logErrorAndExit(err)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
190
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
if (err.code === 'PLT_RUNTIME_RUNTIME_EXIT') {
|
|
192
|
+
console.log('Runtime exited before startup was completed, restarting')
|
|
193
|
+
await sleep(1000)
|
|
194
|
+
return startCommand(args)
|
|
195
|
+
}
|
|
187
196
|
|
|
188
|
-
|
|
197
|
+
if (err.filenames) {
|
|
198
|
+
console.error(`Missing config file!
|
|
199
|
+
Be sure to have a config file with one of the following names:
|
|
189
200
|
|
|
190
|
-
|
|
191
|
-
process.exit(1)
|
|
192
|
-
} else if (err.validationErrors) {
|
|
193
|
-
printConfigValidationErrors(err)
|
|
194
|
-
process.exit(1)
|
|
195
|
-
}
|
|
201
|
+
${err.filenames.map((s) => ' * ' + s).join('\n')}
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
In alternative run "npm create platformatic@latest" to generate a basic plt service config.`)
|
|
204
|
+
process.exit(1)
|
|
205
|
+
} else if (err.validationErrors) {
|
|
206
|
+
printConfigValidationErrors(err)
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
199
209
|
|
|
200
|
-
|
|
201
|
-
console.error(
|
|
202
|
-
|
|
210
|
+
delete err?.stack
|
|
211
|
+
console.error(err?.message)
|
|
212
|
+
|
|
213
|
+
if (err?.cause) {
|
|
214
|
+
console.error(`${err.cause}`)
|
|
215
|
+
}
|
|
203
216
|
|
|
204
|
-
|
|
217
|
+
process.exit(1)
|
|
218
|
+
}
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
module.exports = { buildRuntime, start, startCommand }
|
package/lib/worker.js
CHANGED
|
@@ -98,10 +98,10 @@ process.on('uncaughtException', (err) => {
|
|
|
98
98
|
|
|
99
99
|
if (stop) {
|
|
100
100
|
stop().then(() => {
|
|
101
|
-
process.exit(1)
|
|
101
|
+
setImmediate(process.exit.bind(process), 1)
|
|
102
102
|
})
|
|
103
103
|
} else {
|
|
104
|
-
process.exit(1)
|
|
104
|
+
setImmediate(process.exit.bind(process), 1)
|
|
105
105
|
}
|
|
106
106
|
})
|
|
107
107
|
|
|
@@ -112,10 +112,10 @@ process.on('unhandledRejection', (err) => {
|
|
|
112
112
|
|
|
113
113
|
if (stop) {
|
|
114
114
|
stop().then(() => {
|
|
115
|
-
process.exit(1)
|
|
115
|
+
setImmediate(process.exit.bind(process), 1)
|
|
116
116
|
})
|
|
117
117
|
} else {
|
|
118
|
-
process.exit(1)
|
|
118
|
+
setImmediate(process.exit.bind(process), 1)
|
|
119
119
|
}
|
|
120
120
|
})
|
|
121
121
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@fastify/express": "^
|
|
20
|
+
"@fastify/express": "^3.0.0",
|
|
21
21
|
"@fastify/formbody": "^7.4.0",
|
|
22
22
|
"@matteo.collina/tspl": "^0.1.1",
|
|
23
23
|
"borp": "^0.11.0",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"execa": "^8.0.1",
|
|
26
26
|
"express": "^4.18.3",
|
|
27
27
|
"fast-jwt": "^3.3.3",
|
|
28
|
+
"get-port": "^7.1.0",
|
|
28
29
|
"pino-abstract-transport": "^1.1.0",
|
|
29
30
|
"snazzy": "^9.0.0",
|
|
30
31
|
"split2": "^4.2.0",
|
|
@@ -33,11 +34,10 @@
|
|
|
33
34
|
"typescript": "^5.4.2",
|
|
34
35
|
"undici-oidc-interceptor": "^0.5.0",
|
|
35
36
|
"why-is-node-running": "^2.2.2",
|
|
36
|
-
"@platformatic/sql-graphql": "1.
|
|
37
|
-
"@platformatic/sql-mapper": "1.
|
|
37
|
+
"@platformatic/sql-graphql": "1.36.0",
|
|
38
|
+
"@platformatic/sql-mapper": "1.36.0"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"ws": "^8.16.0",
|
|
41
41
|
"@fastify/error": "^3.4.1",
|
|
42
42
|
"@fastify/websocket": "^10.0.0",
|
|
43
43
|
"@hapi/topo": "^6.0.2",
|
|
@@ -57,18 +57,19 @@
|
|
|
57
57
|
"minimist": "^1.2.8",
|
|
58
58
|
"pino": "^8.19.0",
|
|
59
59
|
"pino-pretty": "^10.3.1",
|
|
60
|
-
"semgrator": "^0.3.0",
|
|
61
60
|
"pino-roll": "^1.0.0",
|
|
61
|
+
"semgrator": "^0.3.0",
|
|
62
62
|
"tail-file-stream": "^0.1.0",
|
|
63
63
|
"undici": "^6.9.0",
|
|
64
64
|
"why-is-node-running": "^2.2.2",
|
|
65
|
-
"
|
|
66
|
-
"@platformatic/
|
|
67
|
-
"@platformatic/
|
|
68
|
-
"@platformatic/
|
|
69
|
-
"@platformatic/
|
|
70
|
-
"@platformatic/
|
|
71
|
-
"@platformatic/
|
|
65
|
+
"ws": "^8.16.0",
|
|
66
|
+
"@platformatic/db": "1.36.0",
|
|
67
|
+
"@platformatic/config": "1.36.0",
|
|
68
|
+
"@platformatic/composer": "1.36.0",
|
|
69
|
+
"@platformatic/service": "1.36.0",
|
|
70
|
+
"@platformatic/generators": "1.36.0",
|
|
71
|
+
"@platformatic/telemetry": "1.36.0",
|
|
72
|
+
"@platformatic/utils": "1.36.0"
|
|
72
73
|
},
|
|
73
74
|
"standard": {
|
|
74
75
|
"ignore": [
|