@platformatic/runtime 3.34.1 → 3.35.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/lib/errors.js +4 -0
- package/lib/runtime.js +42 -5
- package/lib/utils.js +33 -0
- package/lib/worker/itc.js +8 -4
- package/package.json +16 -16
- package/schema.json +1 -1
package/lib/errors.js
CHANGED
|
@@ -40,6 +40,10 @@ export const ApplicationStartTimeoutError = createError(
|
|
|
40
40
|
`${ERROR_PREFIX}_APPLICATION_START_TIMEOUT`,
|
|
41
41
|
"Application with id '%s' failed to start in %dms."
|
|
42
42
|
)
|
|
43
|
+
export const ApplicationsDependenciesCycleError = createError(
|
|
44
|
+
`${ERROR_PREFIX}_APPLICATIONS_DEPENDENCIES_CYCLE`,
|
|
45
|
+
'Detected a cycle in the applications dependencies: %s'
|
|
46
|
+
)
|
|
43
47
|
export const FailedToRetrieveOpenAPISchemaError = createError(
|
|
44
48
|
`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_OPENAPI_SCHEMA`,
|
|
45
49
|
'Failed to retrieve OpenAPI schema for application with id "%s": %s'
|
package/lib/runtime.js
CHANGED
|
@@ -17,6 +17,7 @@ import { existsSync } from 'node:fs'
|
|
|
17
17
|
import { readFile } from 'node:fs/promises'
|
|
18
18
|
import { STATUS_CODES } from 'node:http'
|
|
19
19
|
import { createRequire } from 'node:module'
|
|
20
|
+
import { availableParallelism } from 'node:os'
|
|
20
21
|
import { dirname, isAbsolute, join } from 'node:path'
|
|
21
22
|
import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
|
|
22
23
|
import { pathToFileURL } from 'node:url'
|
|
@@ -45,6 +46,7 @@ import { createChannelCreationHook } from './policies.js'
|
|
|
45
46
|
import { startPrometheusServer } from './prom-server.js'
|
|
46
47
|
import { startScheduler } from './scheduler.js'
|
|
47
48
|
import { createSharedStore } from './shared-http-cache.js'
|
|
49
|
+
import { topologicalSort } from './utils.js'
|
|
48
50
|
import { version } from './version.js'
|
|
49
51
|
import { DynamicWorkersScaler } from './worker-scaler.js'
|
|
50
52
|
import { HealthSignalsQueue } from './worker/health-signals.js'
|
|
@@ -90,7 +92,7 @@ function parseOrigins (origins) {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
// Always run operations in parallel to avoid deadlocks when services have dependencies
|
|
93
|
-
const MAX_CONCURRENCY =
|
|
95
|
+
const MAX_CONCURRENCY = availableParallelism()
|
|
94
96
|
const MAX_BOOTSTRAP_ATTEMPTS = 5
|
|
95
97
|
const IMMEDIATE_RESTART_MAX_THRESHOLD = 10
|
|
96
98
|
const MAX_WORKERS = 100
|
|
@@ -157,7 +159,9 @@ export class Runtime extends EventEmitter {
|
|
|
157
159
|
this.#meshInterceptor = createThreadInterceptor({
|
|
158
160
|
domain: '.plt.local',
|
|
159
161
|
timeout: this.#config.applicationTimeout,
|
|
160
|
-
|
|
162
|
+
meshTimeout: this.#context.meshTimeout ?? true,
|
|
163
|
+
onChannelCreation: this.#channelCreationHook,
|
|
164
|
+
onError: this.#onMeshInterceptorError.bind(this)
|
|
161
165
|
})
|
|
162
166
|
this.logger = abstractLogger // This is replaced by the real logger in init() and eventually removed in close()
|
|
163
167
|
this.#status = undefined
|
|
@@ -543,9 +547,21 @@ export class Runtime extends EventEmitter {
|
|
|
543
547
|
return removed
|
|
544
548
|
}
|
|
545
549
|
|
|
546
|
-
async startApplications (
|
|
550
|
+
async startApplications (applications, silent = false) {
|
|
551
|
+
// For each worker, get its dependencies from the first worker
|
|
552
|
+
const dependencies = new Map()
|
|
553
|
+
for (const applicationId of applications) {
|
|
554
|
+
const worker = await this.#getWorkerByIdOrNext(applicationId, 0)
|
|
555
|
+
|
|
556
|
+
dependencies.set(applicationId, await sendViaITC(worker, 'getDependencies'))
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Now, topological sort the applications based on their dependencies.
|
|
560
|
+
// If circular dependencies are detected, an error with proper error code is thrown.
|
|
561
|
+
applications = topologicalSort(dependencies)
|
|
562
|
+
|
|
547
563
|
const startInvocations = []
|
|
548
|
-
for (const application of
|
|
564
|
+
for (const application of applications) {
|
|
549
565
|
startInvocations.push([application, silent])
|
|
550
566
|
}
|
|
551
567
|
|
|
@@ -1835,7 +1851,16 @@ export class Runtime extends EventEmitter {
|
|
|
1835
1851
|
worker[kInterceptorReadyPromise] = this.#meshInterceptor.route(applicationId, worker)
|
|
1836
1852
|
|
|
1837
1853
|
// Wait for initialization
|
|
1838
|
-
|
|
1854
|
+
try {
|
|
1855
|
+
await waitEventFromITC(worker, 'init')
|
|
1856
|
+
} catch (e) {
|
|
1857
|
+
if (e.code !== 'PLT_RUNTIME_APPLICATION_WORKER_EXIT') {
|
|
1858
|
+
this.logger.error({ err: ensureLoggableError(e) }, `Failed to initialize the ${errorLabel}. Replacing it ...`)
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
this.#workers.delete(workerId)
|
|
1862
|
+
return this.#setupWorker(config, applicationConfig, workersCount, applicationId, index, enabled)
|
|
1863
|
+
}
|
|
1839
1864
|
|
|
1840
1865
|
if (applicationConfig.entrypoint) {
|
|
1841
1866
|
this.#entrypointId = applicationId
|
|
@@ -2988,4 +3013,16 @@ export class Runtime extends EventEmitter {
|
|
|
2988
3013
|
|
|
2989
3014
|
this.#loggerContext.updatePrefixes(ids)
|
|
2990
3015
|
}
|
|
3016
|
+
|
|
3017
|
+
#onMeshInterceptorError (error) {
|
|
3018
|
+
const worker = error.port
|
|
3019
|
+
|
|
3020
|
+
this.logger.error(
|
|
3021
|
+
{ err: ensureLoggableError(error.cause) },
|
|
3022
|
+
`The ${this.#workerExtendedLabel(worker[kApplicationId], worker[kWorkerId])} threw an error during mesh network setup. Replacing it ...`
|
|
3023
|
+
)
|
|
3024
|
+
|
|
3025
|
+
this.emit('application:worker:init:failed', { application: worker[kApplicationId], worker: worker[kWorkerId] })
|
|
3026
|
+
worker.terminate()
|
|
3027
|
+
}
|
|
2991
3028
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
+
import { ApplicationsDependenciesCycleError } from './errors.js'
|
|
4
5
|
|
|
5
6
|
export function getArrayDifference (a, b) {
|
|
6
7
|
return a.filter(element => {
|
|
@@ -17,3 +18,35 @@ export function getRuntimeTmpDir (runtimeDir) {
|
|
|
17
18
|
const runtimeDirHash = createHash('md5').update(runtimeDir).digest('hex')
|
|
18
19
|
return join(platformaticTmpDir, runtimeDirHash)
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
// Graph: Map<string, string[]>
|
|
23
|
+
export function topologicalSort (graph) {
|
|
24
|
+
const result = []
|
|
25
|
+
const visited = new Set()
|
|
26
|
+
const path = []
|
|
27
|
+
|
|
28
|
+
function visit (node) {
|
|
29
|
+
if (visited.has(node)) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (path.includes(node)) {
|
|
34
|
+
throw new ApplicationsDependenciesCycleError(path.concat([node]).join(' -> '))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
path.push(node)
|
|
38
|
+
for (const dep of graph.get(node)) {
|
|
39
|
+
visit(dep)
|
|
40
|
+
}
|
|
41
|
+
path.pop()
|
|
42
|
+
|
|
43
|
+
visited.add(node)
|
|
44
|
+
result.push(node)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const node of graph.keys()) {
|
|
48
|
+
visit(node)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|
package/lib/worker/itc.js
CHANGED
|
@@ -2,8 +2,8 @@ import { ensureLoggableError, executeInParallel, executeWithTimeout, kTimeout }
|
|
|
2
2
|
import { ITC } from '@platformatic/itc'
|
|
3
3
|
import { Unpromise } from '@watchable/unpromise'
|
|
4
4
|
import { once } from 'node:events'
|
|
5
|
-
import { Duplex } from 'node:stream'
|
|
6
5
|
import { createRequire } from 'node:module'
|
|
6
|
+
import { Duplex } from 'node:stream'
|
|
7
7
|
import { parentPort, workerData } from 'node:worker_threads'
|
|
8
8
|
import {
|
|
9
9
|
ApplicationExitedError,
|
|
@@ -49,7 +49,7 @@ function startSubprocessRepl (port, childManager, clientWs, controller) {
|
|
|
49
49
|
childManager.on('repl:exit', handleReplExit)
|
|
50
50
|
|
|
51
51
|
// Forward input from MessagePort to child process
|
|
52
|
-
port.on('message',
|
|
52
|
+
port.on('message', message => {
|
|
53
53
|
if (message.type === 'input') {
|
|
54
54
|
childManager.send(clientWs, 'replInput', { data: message.data }).catch(() => {
|
|
55
55
|
// Ignore errors if the child process has exited
|
|
@@ -212,6 +212,10 @@ export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
|
212
212
|
})
|
|
213
213
|
},
|
|
214
214
|
|
|
215
|
+
async getDependencies () {
|
|
216
|
+
return controller.capability.getDependencies?.() ?? []
|
|
217
|
+
},
|
|
218
|
+
|
|
215
219
|
async build () {
|
|
216
220
|
return controller.capability.build()
|
|
217
221
|
},
|
|
@@ -357,13 +361,13 @@ export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
|
357
361
|
// Create a duplex stream that wraps the MessagePort
|
|
358
362
|
const replStream = new Duplex({
|
|
359
363
|
read () {},
|
|
360
|
-
write (chunk,
|
|
364
|
+
write (chunk, _, callback) {
|
|
361
365
|
port.postMessage({ type: 'output', data: chunk.toString() })
|
|
362
366
|
callback()
|
|
363
367
|
}
|
|
364
368
|
})
|
|
365
369
|
|
|
366
|
-
port.on('message',
|
|
370
|
+
port.on('message', message => {
|
|
367
371
|
if (message.type === 'input') {
|
|
368
372
|
replStream.push(message.data)
|
|
369
373
|
} else if (message.type === 'close') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.35.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"typescript": "^5.5.4",
|
|
36
36
|
"undici-oidc-interceptor": "^0.5.0",
|
|
37
37
|
"why-is-node-running": "^2.2.2",
|
|
38
|
-
"@platformatic/composer": "3.
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/service": "3.
|
|
43
|
-
"@platformatic/sql-graphql": "3.
|
|
44
|
-
"@platformatic/sql-mapper": "3.
|
|
45
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
38
|
+
"@platformatic/composer": "3.35.1",
|
|
39
|
+
"@platformatic/db": "3.35.1",
|
|
40
|
+
"@platformatic/node": "3.35.1",
|
|
41
|
+
"@platformatic/gateway": "3.35.1",
|
|
42
|
+
"@platformatic/service": "3.35.1",
|
|
43
|
+
"@platformatic/sql-graphql": "3.35.1",
|
|
44
|
+
"@platformatic/sql-mapper": "3.35.1",
|
|
45
|
+
"@platformatic/wattpm-pprof-capture": "3.35.1"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"sonic-boom": "^4.2.0",
|
|
70
70
|
"systeminformation": "^5.27.11",
|
|
71
71
|
"undici": "^7.0.0",
|
|
72
|
-
"undici-thread-interceptor": "^1.
|
|
72
|
+
"undici-thread-interceptor": "^1.3.0",
|
|
73
73
|
"ws": "^8.16.0",
|
|
74
|
-
"@platformatic/
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/generators": "3.
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/
|
|
74
|
+
"@platformatic/foundation": "3.35.1",
|
|
75
|
+
"@platformatic/basic": "3.35.1",
|
|
76
|
+
"@platformatic/generators": "3.35.1",
|
|
77
|
+
"@platformatic/itc": "3.35.1",
|
|
78
|
+
"@platformatic/metrics": "3.35.1",
|
|
79
|
+
"@platformatic/telemetry": "3.35.1"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.35.1.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|