@platformatic/runtime 3.0.0-alpha.8 → 3.0.0-rc.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/config.d.ts CHANGED
@@ -133,6 +133,7 @@ export type PlatformaticRuntimeConfig = {
133
133
  };
134
134
  startTimeout?: number;
135
135
  restartOnError?: boolean | number;
136
+ exitOnUnhandledErrors?: boolean;
136
137
  gracefulShutdown?: {
137
138
  runtime: number | string;
138
139
  application: number | string;
package/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  loadConfigurationModule,
9
9
  loadConfiguration as utilsLoadConfiguration
10
10
  } from '@platformatic/foundation'
11
+ import closeWithGrace from 'close-with-grace'
11
12
  import inspector from 'node:inspector'
12
13
  import { transform, wrapInRuntimeConfig } from './lib/config.js'
13
14
  import { NodeInspectorFlagsNotSupportedError } from './lib/errors.js'
@@ -25,12 +26,32 @@ async function restartRuntime (runtime) {
25
26
  }
26
27
  }
27
28
 
28
- function handleSignal (runtime) {
29
+ function handleSignal (runtime, config) {
30
+ // The very first time we add a listener for SIGUSR2,
31
+ // ignore it since it comes from close-with-grace and we want to use to restart the runtime
32
+ function filterCloseWithGraceSIGUSR2 (event, listener) {
33
+ if (event === 'SIGUSR2') {
34
+ process.removeListener('SIGUSR2', listener)
35
+ process.removeListener('newListener', filterCloseWithGraceSIGUSR2)
36
+ }
37
+ }
38
+
39
+ process.on('newListener', filterCloseWithGraceSIGUSR2)
40
+
41
+ const cwg = closeWithGrace({ delay: config.gracefulShutdown?.runtime ?? 10000 }, async event => {
42
+ if (event.err instanceof Error) {
43
+ console.error(event.err)
44
+ }
45
+ await runtime.close()
46
+ })
47
+
29
48
  /* c8 ignore next 3 */
30
49
  const restartListener = restartRuntime.bind(null, runtime)
31
50
  process.on('SIGUSR2', restartListener)
51
+
32
52
  runtime.on('closed', () => {
33
53
  process.removeListener('SIGUSR2', restartListener)
54
+ cwg.uninstall()
34
55
  })
35
56
  }
36
57
 
@@ -109,7 +130,7 @@ export async function create (configOrRoot, sourceOrConfig, context) {
109
130
  }
110
131
 
111
132
  let runtime = new Runtime(config, context)
112
- handleSignal(runtime)
133
+ handleSignal(runtime, config)
113
134
 
114
135
  // Handle port handling
115
136
  if (context?.start) {
@@ -135,7 +156,7 @@ export async function create (configOrRoot, sourceOrConfig, context) {
135
156
 
136
157
  config.server.port = ++port
137
158
  runtime = new Runtime(config, context)
138
- handleSignal(runtime)
159
+ handleSignal(runtime, config)
139
160
  }
140
161
  }
141
162
  }
package/lib/errors.js CHANGED
@@ -122,3 +122,8 @@ export const MessagingError = createError(
122
122
  `${ERROR_PREFIX}_MESSAGING_ERROR`,
123
123
  'Cannot send a message to application "%s": %s'
124
124
  )
125
+
126
+ export const MissingPprofCapture = createError(
127
+ `${ERROR_PREFIX}_MISSING_PPROF_CAPTURE`,
128
+ 'Please install @platformatic/wattpm-pprof-capture'
129
+ )
@@ -19,13 +19,21 @@ const DEFAULT_LIVENESS_FAIL_BODY = 'ERR'
19
19
  async function checkReadiness (runtime) {
20
20
  const workers = await runtime.getWorkers()
21
21
 
22
- // check if all workers are started
22
+ // Make sure there is at least one started worker
23
+ const applications = new Set()
24
+ const started = new Set()
23
25
  for (const worker of Object.values(workers)) {
24
- if (worker.status !== 'started') {
25
- return { status: false }
26
+ applications.add(worker.application)
27
+
28
+ if (worker.status === 'started') {
29
+ started.add(worker.application)
26
30
  }
27
31
  }
28
32
 
33
+ if (started.size !== applications.size) {
34
+ return { status: false }
35
+ }
36
+
29
37
  // perform custom readiness checks, get custom response content if any
30
38
  const checks = await runtime.getCustomReadinessChecks()
31
39
 
package/lib/runtime.js CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  InvalidArgumentError,
32
32
  MessagingError,
33
33
  MissingEntrypointError,
34
+ MissingPprofCapture,
34
35
  RuntimeAbortedError,
35
36
  RuntimeExitedError,
36
37
  WorkerNotFoundError
@@ -514,12 +515,14 @@ export class Runtime extends EventEmitter {
514
515
 
515
516
  async startApplicationProfiling (id, options = {}, ensureStarted = true) {
516
517
  const service = await this.#getApplicationById(id, ensureStarted)
518
+ this.#validatePprofCapturePreload()
517
519
 
518
520
  return sendViaITC(service, 'startProfiling', options)
519
521
  }
520
522
 
521
523
  async stopApplicationProfiling (id, ensureStarted = true) {
522
524
  const service = await this.#getApplicationById(id, ensureStarted)
525
+ this.#validatePprofCapturePreload()
523
526
 
524
527
  return sendViaITC(service, 'stopProfiling')
525
528
  }
@@ -810,7 +813,9 @@ export class Runtime extends EventEmitter {
810
813
  const label = `${application}:${i}`
811
814
  const worker = this.#workers.get(label)
812
815
 
813
- status[label] = await sendViaITC(worker, 'getCustomHealthCheck')
816
+ if (worker) {
817
+ status[label] = await sendViaITC(worker, 'getCustomHealthCheck')
818
+ }
814
819
  }
815
820
  }
816
821
 
@@ -825,7 +830,9 @@ export class Runtime extends EventEmitter {
825
830
  const label = `${application}:${i}`
826
831
  const worker = this.#workers.get(label)
827
832
 
828
- status[label] = await sendViaITC(worker, 'getCustomReadinessCheck')
833
+ if (worker) {
834
+ status[label] = await sendViaITC(worker, 'getCustomReadinessCheck')
835
+ }
829
836
  }
830
837
  }
831
838
 
@@ -1689,7 +1696,7 @@ export class Runtime extends EventEmitter {
1689
1696
  this.logger.info(`Stopping the ${label}...`)
1690
1697
  }
1691
1698
 
1692
- const exitTimeout = this.#config.gracefulShutdown.runtime
1699
+ const exitTimeout = this.#config.gracefulShutdown.application
1693
1700
  const exitPromise = once(worker, 'exit')
1694
1701
 
1695
1702
  // Always send the stop message, it will shut down workers that only had ITC and interceptors setup
@@ -2374,4 +2381,12 @@ export class Runtime extends EventEmitter {
2374
2381
  }
2375
2382
  return report
2376
2383
  }
2384
+
2385
+ #validatePprofCapturePreload () {
2386
+ const found = this.#config.preload?.some(p => p.includes('wattpm-pprof-capture'))
2387
+
2388
+ if (!found) {
2389
+ throw new MissingPprofCapture()
2390
+ }
2391
+ }
2377
2392
  }
@@ -177,8 +177,18 @@ async function main () {
177
177
  !!config.watch
178
178
  )
179
179
 
180
- process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
181
- process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
180
+ if (config.exitOnUnhandledErrors) {
181
+ process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
182
+ process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
183
+
184
+ process.on('newListener', event => {
185
+ if (event === 'uncaughtException' || event === 'unhandledRejection') {
186
+ globalThis.platformatic.logger.warn(
187
+ `A listener has been added for the "process.${event}" event. This listener will be never triggered as Watt default behavior will kill the process before.\n To disable this behavior, set "exitOnUnhandledErrors" to false in the runtime config.`
188
+ )
189
+ }
190
+ })
191
+ }
182
192
 
183
193
  await controller.init()
184
194
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0-rc.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -34,14 +34,14 @@
34
34
  "typescript": "^5.5.4",
35
35
  "undici-oidc-interceptor": "^0.5.0",
36
36
  "why-is-node-running": "^2.2.2",
37
- "@platformatic/composer": "3.0.0-alpha.8",
38
- "@platformatic/gateway": "3.0.0-alpha.8",
39
- "@platformatic/db": "3.0.0-alpha.8",
40
- "@platformatic/node": "3.0.0-alpha.8",
41
- "@platformatic/service": "3.0.0-alpha.8",
42
- "@platformatic/sql-graphql": "3.0.0-alpha.8",
43
- "@platformatic/sql-mapper": "3.0.0-alpha.8",
44
- "@platformatic/wattpm-pprof-capture": "3.0.0-alpha.8"
37
+ "@platformatic/db": "3.0.0-rc.1",
38
+ "@platformatic/composer": "3.0.0-rc.1",
39
+ "@platformatic/gateway": "3.0.0-rc.1",
40
+ "@platformatic/node": "3.0.0-rc.1",
41
+ "@platformatic/sql-graphql": "3.0.0-rc.1",
42
+ "@platformatic/service": "3.0.0-rc.1",
43
+ "@platformatic/sql-mapper": "3.0.0-rc.1",
44
+ "@platformatic/wattpm-pprof-capture": "3.0.0-rc.1"
45
45
  },
46
46
  "dependencies": {
47
47
  "@fastify/accepts": "^5.0.0",
@@ -51,7 +51,7 @@
51
51
  "@platformatic/undici-cache-memory": "^0.8.1",
52
52
  "@watchable/unpromise": "^1.0.2",
53
53
  "change-case-all": "^2.1.0",
54
- "close-with-grace": "^2.0.0",
54
+ "close-with-grace": "^2.2.0",
55
55
  "colorette": "^2.0.20",
56
56
  "cron": "^4.1.0",
57
57
  "debounce": "^2.0.0",
@@ -70,12 +70,12 @@
70
70
  "undici": "^7.0.0",
71
71
  "undici-thread-interceptor": "^0.14.0",
72
72
  "ws": "^8.16.0",
73
- "@platformatic/basic": "3.0.0-alpha.8",
74
- "@platformatic/generators": "3.0.0-alpha.8",
75
- "@platformatic/itc": "3.0.0-alpha.8",
76
- "@platformatic/telemetry": "3.0.0-alpha.8",
77
- "@platformatic/metrics": "3.0.0-alpha.8",
78
- "@platformatic/foundation": "3.0.0-alpha.8"
73
+ "@platformatic/basic": "3.0.0-rc.1",
74
+ "@platformatic/foundation": "3.0.0-rc.1",
75
+ "@platformatic/generators": "3.0.0-rc.1",
76
+ "@platformatic/metrics": "3.0.0-rc.1",
77
+ "@platformatic/telemetry": "3.0.0-rc.1",
78
+ "@platformatic/itc": "3.0.0-rc.1"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=22.18.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.0.0-alpha.8.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.0.0-rc.1.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -1217,6 +1217,10 @@
1217
1217
  }
1218
1218
  ]
1219
1219
  },
1220
+ "exitOnUnhandledErrors": {
1221
+ "default": true,
1222
+ "type": "boolean"
1223
+ },
1220
1224
  "gracefulShutdown": {
1221
1225
  "type": "object",
1222
1226
  "properties": {