@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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.20.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{PORT}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const { setTimeout: sleep } = require('timers/promises')
4
+
5
+ module.exports = async function (fastify, options) {
6
+ setImmediate(() => {
7
+ throw new Error('Crash!')
8
+ })
9
+
10
+ await sleep(1000)
11
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.20.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{PORT}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (fastify, options) {
4
+ fastify.post('/crash', async (_, reply) => {
5
+ setImmediate(() => {
6
+ throw new Error('Crash!')
7
+ })
8
+ })
9
+ }
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
- const worker = new Worker(kWorkerFile, {
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 exited = null
60
- let isWorkerAlive = true
61
- worker.on('exit', (code) => {
62
- // TODO(mcollina): refactor to not set this here
63
- process.exitCode = code
64
- isWorkerAlive = false
65
- configManager.fileWatcher?.stopWatching()
66
- managementApi?.close()
67
- if (typeof exited === 'function') {
68
- exited()
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
- function logErrorAndExit (err) {
184
- if (err.filenames) {
185
- console.error(`Missing config file!
186
- Be sure to have a config file with one of the following names:
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
- ${err.filenames.map((s) => ' * ' + s).join('\n')}
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
- In alternative run "npm create platformatic@latest" to generate a basic plt service config.`)
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
- delete err?.stack
198
- console.error(err?.message)
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
- if (err?.cause) {
201
- console.error(`${err.cause}`)
202
- }
210
+ delete err?.stack
211
+ console.error(err?.message)
212
+
213
+ if (err?.cause) {
214
+ console.error(`${err.cause}`)
215
+ }
203
216
 
204
- process.exit(1)
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.35.5",
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": "^2.3.0",
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.35.5",
37
- "@platformatic/sql-mapper": "1.35.5"
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
- "@platformatic/config": "1.35.5",
66
- "@platformatic/generators": "1.35.5",
67
- "@platformatic/db": "1.35.5",
68
- "@platformatic/service": "1.35.5",
69
- "@platformatic/utils": "1.35.5",
70
- "@platformatic/telemetry": "1.35.5",
71
- "@platformatic/composer": "1.35.5"
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": [