@platformatic/runtime 3.55.0 → 3.56.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/index.d.ts +12 -1
- package/index.js +1 -0
- package/lib/config.js +3 -11
- package/lib/errors.js +4 -0
- package/lib/generator.js +39 -25
- package/lib/loopback-messaging.js +78 -0
- package/lib/management-api.js +3 -3
- package/lib/runtime.js +32 -8
- package/lib/utils.js +12 -3
- package/lib/worker/controller.js +42 -12
- package/lib/worker/health-signals.js +4 -2
- package/lib/worker/http-cache.js +5 -5
- package/lib/worker/interceptors.js +79 -19
- package/lib/worker/itc.js +16 -6
- package/lib/worker/main.js +22 -16
- package/lib/worker/management.js +3 -2
- package/lib/worker/messaging.js +23 -15
- package/lib/worker/shared-context.js +5 -3
- package/lib/worker/symbols.js +1 -2
- package/package.json +17 -16
- package/schema.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FastifyError } from '@fastify/error'
|
|
2
2
|
import { Configuration, ConfigurationOptions, logFatalError, parseArgs } from '@platformatic/foundation'
|
|
3
3
|
import { BaseGenerator } from '@platformatic/generators'
|
|
4
|
+
import { PlatformaticGlobal } from '@platformatic/globals'
|
|
4
5
|
import { JSONSchemaType } from 'ajv'
|
|
5
6
|
import * as colorette from 'colorette'
|
|
6
7
|
import { Logger } from 'pino'
|
|
@@ -27,6 +28,12 @@ export interface ApplicationsCommands {
|
|
|
27
28
|
help: Record<string, string | (() => string)>
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export interface LoopbackMessagingOptions {
|
|
32
|
+
logger?: Logger
|
|
33
|
+
mount?: boolean
|
|
34
|
+
runtimeConfig?: { messagingTimeout?: number; [key: string]: unknown }
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
export namespace errors {
|
|
31
38
|
export const RuntimeExitedError: () => FastifyError
|
|
32
39
|
export const UnknownRuntimeAPICommandError: (command: string) => FastifyError
|
|
@@ -61,7 +68,6 @@ export namespace symbols {
|
|
|
61
68
|
export const kWorkerStatus: unique symbol
|
|
62
69
|
export const kWorkerHealthSignals: unique symbol
|
|
63
70
|
export const kStderrMarker: string
|
|
64
|
-
export const kInterceptors: unique symbol
|
|
65
71
|
export const kWorkersBroadcast: unique symbol
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -191,3 +197,8 @@ export declare function transform (
|
|
|
191
197
|
): Promise<RuntimeConfiguration>
|
|
192
198
|
|
|
193
199
|
export declare function loadApplicationsCommands (): Promise<ApplicationsCommands>
|
|
200
|
+
|
|
201
|
+
export declare function setupLoopbackMessaging (
|
|
202
|
+
targetId: string,
|
|
203
|
+
options?: LoopbackMessagingOptions
|
|
204
|
+
): PlatformaticGlobal['messaging'] & { unmount: () => void }
|
package/index.js
CHANGED
|
@@ -176,6 +176,7 @@ export async function create (configOrRoot, sourceOrConfig, context) {
|
|
|
176
176
|
export { prepareApplication, transform, wrapInRuntimeConfig } from './lib/config.js'
|
|
177
177
|
export * as errors from './lib/errors.js'
|
|
178
178
|
export { RuntimeGenerator as Generator, WrappedGenerator } from './lib/generator.js'
|
|
179
|
+
export { setupLoopbackMessaging } from './lib/loopback-messaging.js'
|
|
179
180
|
export { Runtime } from './lib/runtime.js'
|
|
180
181
|
export { schema } from './lib/schema.js'
|
|
181
182
|
export * from './lib/version.js'
|
package/lib/config.js
CHANGED
|
@@ -18,8 +18,7 @@ import {
|
|
|
18
18
|
InspectorHostError,
|
|
19
19
|
InspectorPortError,
|
|
20
20
|
InvalidArgumentError,
|
|
21
|
-
InvalidEntrypointError
|
|
22
|
-
MissingEntrypointError
|
|
21
|
+
InvalidEntrypointError
|
|
23
22
|
} from './errors.js'
|
|
24
23
|
import { schema } from './schema.js'
|
|
25
24
|
import { upgrade } from './upgrade.js'
|
|
@@ -438,15 +437,8 @@ export async function transform (config, _, context) {
|
|
|
438
437
|
}
|
|
439
438
|
}
|
|
440
439
|
|
|
441
|
-
if (!hasValidEntrypoint && !context.allowMissingEntrypoint) {
|
|
442
|
-
|
|
443
|
-
throw new InvalidEntrypointError(config.entrypoint)
|
|
444
|
-
} else if (applications.length >= 1) {
|
|
445
|
-
throw new MissingEntrypointError()
|
|
446
|
-
}
|
|
447
|
-
// If there are no applications, and no entrypoint it's an empty app.
|
|
448
|
-
// It won't start, but we should be able to parse and operate on it,
|
|
449
|
-
// like adding other applications.
|
|
440
|
+
if (!hasValidEntrypoint && config.entrypoint && !context.allowMissingEntrypoint) {
|
|
441
|
+
throw new InvalidEntrypointError(config.entrypoint)
|
|
450
442
|
}
|
|
451
443
|
|
|
452
444
|
if (typeof config.metrics === 'boolean') {
|
package/lib/errors.js
CHANGED
|
@@ -49,6 +49,10 @@ export const ApplicationStartTimeoutError = createError(
|
|
|
49
49
|
`${ERROR_PREFIX}_APPLICATION_START_TIMEOUT`,
|
|
50
50
|
"Application with id '%s' failed to start in %dms."
|
|
51
51
|
)
|
|
52
|
+
export const ApplicationDependencyNotFoundError = createError(
|
|
53
|
+
`${ERROR_PREFIX}_APPLICATION_DEPENDENCY_NOT_FOUND`,
|
|
54
|
+
'Application dependency %s not found. Available applications are: %s'
|
|
55
|
+
)
|
|
52
56
|
export const ApplicationsDependenciesCycleError = createError(
|
|
53
57
|
`${ERROR_PREFIX}_APPLICATIONS_DEPENDENCIES_CYCLE`,
|
|
54
58
|
'Detected a cycle in the applications dependencies: %s'
|
package/lib/generator.js
CHANGED
|
@@ -19,17 +19,6 @@ import { transform } from './config.js'
|
|
|
19
19
|
import { schema } from './schema.js'
|
|
20
20
|
import { getArrayDifference } from './utils.js'
|
|
21
21
|
|
|
22
|
-
const wrappableProperties = {
|
|
23
|
-
logger: {
|
|
24
|
-
level: '{PLT_SERVER_LOGGER_LEVEL}'
|
|
25
|
-
},
|
|
26
|
-
server: {
|
|
27
|
-
hostname: '{PLT_SERVER_HOSTNAME}',
|
|
28
|
-
port: '{PORT}'
|
|
29
|
-
},
|
|
30
|
-
managementApi: '{PLT_MANAGEMENT_API}'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
22
|
const engines = {
|
|
34
23
|
node: '>=22.19.0'
|
|
35
24
|
}
|
|
@@ -42,13 +31,36 @@ const NoApplicationNamedError = createError(
|
|
|
42
31
|
)
|
|
43
32
|
const NoEntryPointError = createError(`${ERROR_PREFIX}_NO_ENTRYPOINT`, 'No entrypoint had been defined.')
|
|
44
33
|
|
|
34
|
+
function getRuntimeWrappableProperties (skipServer) {
|
|
35
|
+
const wrappableProperties = {
|
|
36
|
+
logger: {
|
|
37
|
+
level: '{PLT_SERVER_LOGGER_LEVEL}'
|
|
38
|
+
},
|
|
39
|
+
managementApi: '{PLT_MANAGEMENT_API}'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!skipServer) {
|
|
43
|
+
wrappableProperties.server = {
|
|
44
|
+
hostname: '{PLT_SERVER_HOSTNAME}',
|
|
45
|
+
port: '{PORT}'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return wrappableProperties
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
function getRuntimeBaseEnvVars (config) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
const env = {}
|
|
54
|
+
|
|
55
|
+
if (!config.skipServer) {
|
|
56
|
+
env.PLT_SERVER_HOSTNAME = '127.0.0.1'
|
|
57
|
+
env.PORT = config.port || 3042
|
|
51
58
|
}
|
|
59
|
+
|
|
60
|
+
env.PLT_SERVER_LOGGER_LEVEL = config.logLevel || 'info'
|
|
61
|
+
env.PLT_MANAGEMENT_API = true
|
|
62
|
+
|
|
63
|
+
return env
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
export class RuntimeGenerator extends BaseGenerator {
|
|
@@ -195,7 +207,7 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
195
207
|
path: this.config.autoload || this.applicationsFolder,
|
|
196
208
|
exclude: ['docs']
|
|
197
209
|
},
|
|
198
|
-
...
|
|
210
|
+
...getRuntimeWrappableProperties(this.config.skipServer)
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
return config
|
|
@@ -251,13 +263,15 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
251
263
|
return
|
|
252
264
|
}
|
|
253
265
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
if (!this.config.skipServer) {
|
|
267
|
+
// port
|
|
268
|
+
this.questions.push({
|
|
269
|
+
type: 'input',
|
|
270
|
+
name: 'port',
|
|
271
|
+
default: 3042,
|
|
272
|
+
message: 'What port do you want to use?'
|
|
273
|
+
})
|
|
274
|
+
}
|
|
261
275
|
}
|
|
262
276
|
|
|
263
277
|
setApplicationsDirectory () {
|
|
@@ -611,7 +625,7 @@ export class WrappedGenerator extends BaseGenerator {
|
|
|
611
625
|
async #createConfigFile () {
|
|
612
626
|
const config = {
|
|
613
627
|
$schema: `https://schemas.platformatic.dev/${this.module}/${this.platformaticVersion}.json`,
|
|
614
|
-
runtime:
|
|
628
|
+
runtime: getRuntimeWrappableProperties(this.config.skipServer)
|
|
615
629
|
}
|
|
616
630
|
|
|
617
631
|
this.addFile({
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { abstractLogger } from '@platformatic/foundation'
|
|
2
|
+
import { getMessaging, removeGlobals, updateGlobals } from '@platformatic/globals'
|
|
3
|
+
import { MessageChannel } from 'node:worker_threads'
|
|
4
|
+
import { MessagingITC } from './worker/messaging.js'
|
|
5
|
+
|
|
6
|
+
class LoopbackMessagingITC extends MessagingITC {
|
|
7
|
+
#application
|
|
8
|
+
#channel
|
|
9
|
+
|
|
10
|
+
constructor (id, application, runtimeConfig, logger, channel) {
|
|
11
|
+
super(id, runtimeConfig, logger)
|
|
12
|
+
|
|
13
|
+
this.#application = application
|
|
14
|
+
this.#channel = channel
|
|
15
|
+
this.addSource(channel)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Force the next worker to be the channel
|
|
19
|
+
_getNextWorker () {
|
|
20
|
+
return { application: this.#application, channel: this.#channel }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class LoopbackMessaging extends LoopbackMessagingITC {
|
|
25
|
+
#target
|
|
26
|
+
#channel
|
|
27
|
+
#previousGlobals
|
|
28
|
+
|
|
29
|
+
constructor (targetId, config, logger) {
|
|
30
|
+
const channel = new MessageChannel()
|
|
31
|
+
|
|
32
|
+
super('loopback-messaging', targetId, config, logger, channel.port1)
|
|
33
|
+
this.#channel = channel
|
|
34
|
+
this.#target = new LoopbackMessagingITC(targetId, 'loopback-messaging', config, logger, channel.port2)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
mount () {
|
|
38
|
+
this.#previousGlobals = getMessaging({ throwOnMissing: false })
|
|
39
|
+
|
|
40
|
+
updateGlobals({
|
|
41
|
+
messaging: {
|
|
42
|
+
handle: this.#target.handle.bind(this.#target),
|
|
43
|
+
send: this.#target.send.bind(this.#target),
|
|
44
|
+
notify: this.#target.notify.bind(this.#target)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
unmount () {
|
|
52
|
+
if (this.#previousGlobals) {
|
|
53
|
+
updateGlobals({ messaging: this.#previousGlobals })
|
|
54
|
+
} else {
|
|
55
|
+
removeGlobals(['messaging'])
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.close()
|
|
59
|
+
this.#target.close()
|
|
60
|
+
this.#channel.port1.close()
|
|
61
|
+
this.#channel.port2.close()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function setupLoopbackMessaging (targetId, options) {
|
|
66
|
+
options ??= {}
|
|
67
|
+
|
|
68
|
+
const runtimeConfig = { ...(options.runtimeConfig ?? {}) }
|
|
69
|
+
runtimeConfig.messagingTimeout ??= 5000
|
|
70
|
+
|
|
71
|
+
const messaging = new LoopbackMessaging(targetId, runtimeConfig, options.logger ?? abstractLogger)
|
|
72
|
+
|
|
73
|
+
if (options.mount !== false) {
|
|
74
|
+
messaging.mount()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return messaging
|
|
78
|
+
}
|
package/lib/management-api.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@platformatic/foundation'
|
|
10
10
|
import fastify from 'fastify'
|
|
11
11
|
import { platform, tmpdir } from 'node:os'
|
|
12
|
-
import { join } from 'node:path'
|
|
12
|
+
import { dirname, join } from 'node:path'
|
|
13
13
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
14
14
|
import { createWebSocketStream } from 'ws'
|
|
15
15
|
import { prepareApplication } from './config.js'
|
|
@@ -342,8 +342,8 @@ export async function startManagementApi (runtime, config) {
|
|
|
342
342
|
const customSocket = typeof config === 'object' ? config?.socket : null
|
|
343
343
|
|
|
344
344
|
const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
|
|
345
|
-
if (platform() !== 'win32'
|
|
346
|
-
await createDirectory(runtimePIDDir,
|
|
345
|
+
if (platform() !== 'win32') {
|
|
346
|
+
await createDirectory(customSocket ? dirname(customSocket) : runtimePIDDir, typeof customSocket !== 'string')
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
let socketPath = null
|
package/lib/runtime.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
kTimeout,
|
|
10
10
|
parseMemorySize
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
|
+
import { getExecutable } from '@platformatic/globals'
|
|
12
13
|
import { ITC } from '@platformatic/itc'
|
|
13
14
|
import { collectProcessMetrics, client as metricsClient } from '@platformatic/metrics'
|
|
14
15
|
import fastify from 'fastify'
|
|
@@ -36,7 +37,6 @@ import {
|
|
|
36
37
|
CannotRemoveEntrypointError,
|
|
37
38
|
InvalidArgumentError,
|
|
38
39
|
MessagingError,
|
|
39
|
-
MissingEntrypointError,
|
|
40
40
|
MissingPprofCapture,
|
|
41
41
|
RuntimeAbortedError,
|
|
42
42
|
WorkerInterceptorJoinTimeoutError,
|
|
@@ -274,14 +274,18 @@ export class Runtime extends EventEmitter {
|
|
|
274
274
|
await this.init()
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
if (typeof this.#config.entrypoint === 'undefined') {
|
|
278
|
-
throw new MissingEntrypointError()
|
|
279
|
-
}
|
|
280
277
|
this.#updateStatus('starting')
|
|
281
278
|
this.#createWorkersBroadcastChannel()
|
|
282
279
|
|
|
283
280
|
try {
|
|
284
|
-
|
|
281
|
+
const applications = this.getApplicationsIds()
|
|
282
|
+
await this.startApplications(applications, silent)
|
|
283
|
+
|
|
284
|
+
if (applications.length === 0) {
|
|
285
|
+
this.#updateStatus('started')
|
|
286
|
+
await this.close(silent)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
285
289
|
|
|
286
290
|
if (this.#config.inspectorOptions) {
|
|
287
291
|
const { port } = this.#config.inspectorOptions
|
|
@@ -327,7 +331,9 @@ export class Runtime extends EventEmitter {
|
|
|
327
331
|
this.#startHealthMetricsCollectionIfNeeded()
|
|
328
332
|
|
|
329
333
|
await this.#dynamicWorkersScaler?.start()
|
|
330
|
-
this.#
|
|
334
|
+
if (this.#url) {
|
|
335
|
+
this.#showUrl()
|
|
336
|
+
}
|
|
331
337
|
return this.#url
|
|
332
338
|
}
|
|
333
339
|
|
|
@@ -582,7 +588,7 @@ export class Runtime extends EventEmitter {
|
|
|
582
588
|
|
|
583
589
|
// Now, topological sort the applications based on their dependencies.
|
|
584
590
|
// If circular dependencies are detected, an error with proper error code is thrown.
|
|
585
|
-
applications = topologicalSort(dependencies)
|
|
591
|
+
applications = topologicalSort(applications, dependencies)
|
|
586
592
|
|
|
587
593
|
// Group into dependency levels so that each level's dependencies are all
|
|
588
594
|
// in previous levels. Levels are started sequentially, but applications
|
|
@@ -1122,6 +1128,10 @@ export class Runtime extends EventEmitter {
|
|
|
1122
1128
|
}
|
|
1123
1129
|
|
|
1124
1130
|
async getEntrypointDetails () {
|
|
1131
|
+
if (!this.#entrypointId) {
|
|
1132
|
+
return null
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1125
1135
|
return this.getApplicationDetails(this.#entrypointId)
|
|
1126
1136
|
}
|
|
1127
1137
|
|
|
@@ -1168,6 +1178,10 @@ export class Runtime extends EventEmitter {
|
|
|
1168
1178
|
}
|
|
1169
1179
|
|
|
1170
1180
|
async getMetrics (format = 'json') {
|
|
1181
|
+
if (this.#config.metrics === false || this.#config.metrics?.enabled === false) {
|
|
1182
|
+
throw new Error('Metrics are disabled')
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1171
1185
|
let metrics = null
|
|
1172
1186
|
|
|
1173
1187
|
const applicationRestartMetrics = format === 'json'
|
|
@@ -1716,6 +1730,10 @@ export class Runtime extends EventEmitter {
|
|
|
1716
1730
|
}
|
|
1717
1731
|
|
|
1718
1732
|
#showUrl () {
|
|
1733
|
+
if (!this.#url) {
|
|
1734
|
+
return
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1719
1737
|
this.logger.info(`Platformatic is now listening at ${this.#url}`)
|
|
1720
1738
|
}
|
|
1721
1739
|
|
|
@@ -1734,7 +1752,7 @@ export class Runtime extends EventEmitter {
|
|
|
1734
1752
|
applicationConfig.path = join(this.#root, config.resolvedApplicationsBasePath, id)
|
|
1735
1753
|
|
|
1736
1754
|
if (!existsSync(applicationConfig.path)) {
|
|
1737
|
-
const executable =
|
|
1755
|
+
const executable = getExecutable() ?? 'platformatic'
|
|
1738
1756
|
this.logger.error(
|
|
1739
1757
|
`The path for application "%s" does not exist. Please run "${executable} resolve" and try again.`,
|
|
1740
1758
|
id
|
|
@@ -2713,6 +2731,12 @@ export class Runtime extends EventEmitter {
|
|
|
2713
2731
|
|
|
2714
2732
|
async #createWorkersBroadcastChannel () {
|
|
2715
2733
|
this.#workersBroadcastChannel?.close()
|
|
2734
|
+
|
|
2735
|
+
if (this.#config.applications.length === 0) {
|
|
2736
|
+
this.#workersBroadcastChannel = undefined
|
|
2737
|
+
return
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2716
2740
|
this.#workersBroadcastChannel = new BroadcastChannel(kWorkersBroadcast)
|
|
2717
2741
|
}
|
|
2718
2742
|
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +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
|
+
import { ApplicationDependencyNotFoundError, ApplicationsDependenciesCycleError } from './errors.js'
|
|
5
5
|
|
|
6
6
|
export function getArrayDifference (a, b) {
|
|
7
7
|
return a.filter(element => {
|
|
@@ -48,13 +48,22 @@ export function topologicalLevels (sorted, graph) {
|
|
|
48
48
|
return levels
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
52
|
-
export function topologicalSort (graph) {
|
|
51
|
+
// valid: string[] | Map<string, string[]>, graph?: Map<string, string[]>
|
|
52
|
+
export function topologicalSort (valid, graph) {
|
|
53
|
+
if (!graph) {
|
|
54
|
+
graph = valid
|
|
55
|
+
valid = Array.from(graph.keys())
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
const result = []
|
|
54
59
|
const visited = new Set()
|
|
55
60
|
const path = []
|
|
56
61
|
|
|
57
62
|
function visit (node) {
|
|
63
|
+
if (!valid.includes(node)) {
|
|
64
|
+
throw new ApplicationDependencyNotFoundError(node, valid.join(', '))
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
if (visited.has(node)) {
|
|
59
68
|
return
|
|
60
69
|
}
|
package/lib/worker/controller.js
CHANGED
|
@@ -8,6 +8,16 @@ import {
|
|
|
8
8
|
loadConfigurationModule,
|
|
9
9
|
mirrorGlobalDispatcherForBuiltinFetch
|
|
10
10
|
} from '@platformatic/foundation'
|
|
11
|
+
import {
|
|
12
|
+
getLogger,
|
|
13
|
+
getOnActiveResourcesEventLoop,
|
|
14
|
+
getOnHttpStatsConnected,
|
|
15
|
+
getOnHttpStatsFree,
|
|
16
|
+
getOnHttpStatsPending,
|
|
17
|
+
getOnHttpStatsQueued,
|
|
18
|
+
getOnHttpStatsRunning,
|
|
19
|
+
getOnHttpStatsSize
|
|
20
|
+
} from '@platformatic/globals'
|
|
11
21
|
import debounce from 'debounce'
|
|
12
22
|
import { EventEmitter } from 'node:events'
|
|
13
23
|
import { existsSync } from 'node:fs'
|
|
@@ -17,6 +27,7 @@ import { workerData } from 'node:worker_threads'
|
|
|
17
27
|
import { getGlobalDispatcher, setGlobalDispatcher } from 'undici'
|
|
18
28
|
import { ApplicationAlreadyStartedError, RuntimeNotStartedError } from '../errors.js'
|
|
19
29
|
import { getApplicationUrl } from '../utils.js'
|
|
30
|
+
import { markAsPlatformaticDispatcher, refreshGlobalDispatcher } from './interceptors.js'
|
|
20
31
|
|
|
21
32
|
function fetchApplicationUrl (application, key) {
|
|
22
33
|
if (!key.endsWith('_URL') || !application.id) {
|
|
@@ -29,7 +40,8 @@ function fetchApplicationUrl (application, key) {
|
|
|
29
40
|
function handleUnhandled (app, type, err) {
|
|
30
41
|
const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
const logger = getLogger()
|
|
44
|
+
logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
|
|
33
45
|
|
|
34
46
|
executeWithTimeout(app?.stop(), 1000)
|
|
35
47
|
.catch()
|
|
@@ -142,7 +154,8 @@ export class Controller extends EventEmitter {
|
|
|
142
154
|
}
|
|
143
155
|
} catch (err) {
|
|
144
156
|
if (err.validationErrors) {
|
|
145
|
-
|
|
157
|
+
const logger = getLogger()
|
|
158
|
+
logger.error(
|
|
146
159
|
{ err: ensureLoggableError(err) },
|
|
147
160
|
'The application threw a validation error.'
|
|
148
161
|
)
|
|
@@ -193,6 +206,9 @@ export class Controller extends EventEmitter {
|
|
|
193
206
|
|
|
194
207
|
try {
|
|
195
208
|
await this.capability.start({ listen })
|
|
209
|
+
if (refreshGlobalDispatcher()) {
|
|
210
|
+
this.#updateDispatcher()
|
|
211
|
+
}
|
|
196
212
|
this.#listening = listen
|
|
197
213
|
/* c8 ignore next 5 */
|
|
198
214
|
} catch (err) {
|
|
@@ -244,18 +260,29 @@ export class Controller extends EventEmitter {
|
|
|
244
260
|
|
|
245
261
|
async getMetrics ({ format }) {
|
|
246
262
|
const dispatcher = getGlobalDispatcher()
|
|
247
|
-
|
|
263
|
+
const onHttpStatsFree = getOnHttpStatsFree({ throwOnMissing: false })
|
|
264
|
+
|
|
265
|
+
if (onHttpStatsFree && dispatcher?.stats) {
|
|
266
|
+
const onHttpStatsConnected = getOnHttpStatsConnected()
|
|
267
|
+
const onHttpStatsPending = getOnHttpStatsPending()
|
|
268
|
+
const onHttpStatsQueued = getOnHttpStatsQueued()
|
|
269
|
+
const onHttpStatsRunning = getOnHttpStatsRunning()
|
|
270
|
+
const onHttpStatsSize = getOnHttpStatsSize()
|
|
271
|
+
|
|
248
272
|
for (const url in dispatcher.stats) {
|
|
249
273
|
const { free, connected, pending, queued, running, size } = dispatcher.stats[url]
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
274
|
+
onHttpStatsFree(url, free || 0)
|
|
275
|
+
onHttpStatsConnected(url, connected || 0)
|
|
276
|
+
onHttpStatsPending(url, pending || 0)
|
|
277
|
+
onHttpStatsQueued(url, queued || 0)
|
|
278
|
+
onHttpStatsRunning(url, running || 0)
|
|
279
|
+
onHttpStatsSize(url, size || 0)
|
|
256
280
|
}
|
|
257
281
|
}
|
|
258
|
-
|
|
282
|
+
const onActiveResourcesEventLoop = getOnActiveResourcesEventLoop({ throwOnMissing: false })
|
|
283
|
+
if (onActiveResourcesEventLoop) {
|
|
284
|
+
onActiveResourcesEventLoop(getActiveResourcesInfo().length)
|
|
285
|
+
}
|
|
259
286
|
return this.capability.getMetrics({ format })
|
|
260
287
|
}
|
|
261
288
|
|
|
@@ -300,7 +327,8 @@ export class Controller extends EventEmitter {
|
|
|
300
327
|
}
|
|
301
328
|
|
|
302
329
|
#logAndThrow (err) {
|
|
303
|
-
|
|
330
|
+
const logger = getLogger()
|
|
331
|
+
logger.error(
|
|
304
332
|
{ err: ensureLoggableError(err) },
|
|
305
333
|
err[kHandledError] ? err.message : 'The application threw an error.'
|
|
306
334
|
)
|
|
@@ -326,6 +354,7 @@ export class Controller extends EventEmitter {
|
|
|
326
354
|
|
|
327
355
|
const dispatcher = getGlobalDispatcher().compose(interceptor)
|
|
328
356
|
|
|
357
|
+
markAsPlatformaticDispatcher(dispatcher)
|
|
329
358
|
setGlobalDispatcher(dispatcher)
|
|
330
359
|
mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
|
|
331
360
|
}
|
|
@@ -336,7 +365,8 @@ export class Controller extends EventEmitter {
|
|
|
336
365
|
|
|
337
366
|
process.on('newListener', event => {
|
|
338
367
|
if (event === 'uncaughtException' || event === 'unhandledRejection') {
|
|
339
|
-
|
|
368
|
+
const logger = getLogger()
|
|
369
|
+
logger.warn(
|
|
340
370
|
`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.`
|
|
341
371
|
)
|
|
342
372
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getITC, updateGlobals } from '@platformatic/globals'
|
|
1
2
|
import {
|
|
2
3
|
HealthSignalMustBeObjectError,
|
|
3
4
|
HealthSignalTypeMustBeStringError
|
|
@@ -60,7 +61,8 @@ export function initHealthSignalsApi (options = {}) {
|
|
|
60
61
|
isSending = false
|
|
61
62
|
try {
|
|
62
63
|
const signals = queue.getAll()
|
|
63
|
-
|
|
64
|
+
const itc = getITC()
|
|
65
|
+
await itc.send('sendHealthSignals', {
|
|
64
66
|
workerId,
|
|
65
67
|
signals
|
|
66
68
|
})
|
|
@@ -76,5 +78,5 @@ export function initHealthSignalsApi (options = {}) {
|
|
|
76
78
|
return promise
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
updateGlobals({ sendHealthSignal })
|
|
80
82
|
}
|
package/lib/worker/http-cache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { getClientSpansAls, getITC } from '@platformatic/globals'
|
|
1
2
|
import { randomUUID } from 'node:crypto'
|
|
2
3
|
import { Readable, Writable } from 'node:stream'
|
|
3
4
|
import { interceptors } from 'undici'
|
|
4
|
-
import { kITC } from './symbols.js'
|
|
5
5
|
|
|
6
6
|
const kCacheIdHeader = Symbol('cacheIdHeader')
|
|
7
7
|
const CACHE_ID_HEADER = 'x-plt-http-cache-id'
|
|
@@ -28,7 +28,7 @@ export class RemoteCacheStore {
|
|
|
28
28
|
this.#logger.error(err, 'Error in onRequest http cache hook')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const itc =
|
|
31
|
+
const itc = getITC()
|
|
32
32
|
if (!itc) return
|
|
33
33
|
|
|
34
34
|
const sanitizedRequest = this.#sanitizeRequest(request)
|
|
@@ -68,7 +68,7 @@ export class RemoteCacheStore {
|
|
|
68
68
|
value.headers = { ...value.headers, [CACHE_ID_HEADER]: cacheEntryId }
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const itc =
|
|
71
|
+
const itc = getITC()
|
|
72
72
|
if (!itc) throw new Error('Cannot write to cache without an ITC instance')
|
|
73
73
|
|
|
74
74
|
const acc = []
|
|
@@ -96,7 +96,7 @@ export class RemoteCacheStore {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
delete (request) {
|
|
99
|
-
const itc =
|
|
99
|
+
const itc = getITC()
|
|
100
100
|
if (!itc) throw new Error('Cannot delete from cache without an ITC instance')
|
|
101
101
|
|
|
102
102
|
request = this.#sanitizeRequest(request)
|
|
@@ -120,7 +120,7 @@ export function httpCacheInterceptor (interceptorOpts) {
|
|
|
120
120
|
|
|
121
121
|
// AsyncLocalStorage that contains a client http request span
|
|
122
122
|
// Exists only when the nodejs capability telemetry is enabled
|
|
123
|
-
const clientSpansAls =
|
|
123
|
+
const clientSpansAls = getClientSpansAls({ throwOnMissing: false })
|
|
124
124
|
|
|
125
125
|
return originalDispatch => {
|
|
126
126
|
const dispatch = (opts, handler) => {
|
|
@@ -1,14 +1,46 @@
|
|
|
1
|
-
import { mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
|
|
1
|
+
import { getGlobalDispatcherFromKnownUndiciSymbols, mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
|
|
2
|
+
import {
|
|
3
|
+
getInterceptors,
|
|
4
|
+
getLogger,
|
|
5
|
+
getOnHttpCacheHit,
|
|
6
|
+
getOnHttpCacheMiss,
|
|
7
|
+
getOnHttpCacheRequest
|
|
8
|
+
} from '@platformatic/globals'
|
|
2
9
|
import { createTelemetryThreadInterceptorHooks } from '@platformatic/telemetry'
|
|
3
10
|
import { createRequire } from 'node:module'
|
|
4
11
|
import { join } from 'node:path'
|
|
5
12
|
import { pathToFileURL } from 'node:url'
|
|
6
13
|
import { parentPort, workerData } from 'node:worker_threads'
|
|
7
|
-
import { Agent, Client, Pool
|
|
14
|
+
import { Agent, Client, Pool } from 'undici'
|
|
8
15
|
import { wire } from 'undici-thread-interceptor'
|
|
9
16
|
import { createChannelCreationHook } from '../policies.js'
|
|
10
17
|
import { RemoteCacheStore, httpCacheInterceptor } from './http-cache.js'
|
|
11
|
-
|
|
18
|
+
|
|
19
|
+
const kPlatformaticGlobalDispatcher = Symbol.for('platformatic.undici.globalDispatcher')
|
|
20
|
+
|
|
21
|
+
let composeRuntimeDispatcher
|
|
22
|
+
|
|
23
|
+
export function markAsPlatformaticDispatcher (dispatcher) {
|
|
24
|
+
Object.defineProperty(dispatcher, kPlatformaticGlobalDispatcher, {
|
|
25
|
+
value: true,
|
|
26
|
+
enumerable: false,
|
|
27
|
+
configurable: true
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function refreshGlobalDispatcher () {
|
|
32
|
+
if (!composeRuntimeDispatcher) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dispatcher = getGlobalDispatcherFromKnownUndiciSymbols()
|
|
37
|
+
if (!dispatcher || dispatcher[kPlatformaticGlobalDispatcher]) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
composeRuntimeDispatcher(dispatcher)
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
12
44
|
|
|
13
45
|
export async function setDispatcher (runtimeConfig) {
|
|
14
46
|
const threadDispatcher = createThreadInterceptor(runtimeConfig)
|
|
@@ -28,17 +60,39 @@ export async function setDispatcher (runtimeConfig) {
|
|
|
28
60
|
|
|
29
61
|
const dispatcherOpts = await getDispatcherOpts(runtimeConfig.undici)
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
function installDispatcher (baseDispatcher) {
|
|
64
|
+
const dispatcher = baseDispatcher.compose(
|
|
65
|
+
[threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
markAsPlatformaticDispatcher(dispatcher)
|
|
69
|
+
mirrorGlobalDispatcherForBuiltinFetch(dispatcher, createLegacyDispatcher(dispatcher))
|
|
70
|
+
return dispatcher
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
composeRuntimeDispatcher = installDispatcher
|
|
74
|
+
installDispatcher(new Agent(dispatcherOpts))
|
|
36
75
|
|
|
37
76
|
return { threadDispatcher }
|
|
38
77
|
}
|
|
39
78
|
|
|
79
|
+
function createLegacyDispatcher (dispatcher) {
|
|
80
|
+
const legacyDispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]
|
|
81
|
+
const currentDispatcher = globalThis[Symbol.for('undici.globalDispatcher.2')]
|
|
82
|
+
|
|
83
|
+
if (legacyDispatcher && legacyDispatcher !== currentDispatcher && legacyDispatcher.constructor?.name === 'Dispatcher1Wrapper') {
|
|
84
|
+
try {
|
|
85
|
+
return new legacyDispatcher.constructor(dispatcher)
|
|
86
|
+
} catch {
|
|
87
|
+
return dispatcher
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return dispatcher
|
|
92
|
+
}
|
|
93
|
+
|
|
40
94
|
export async function updateUndiciInterceptors (undiciConfig) {
|
|
41
|
-
const updatableInterceptors =
|
|
95
|
+
const updatableInterceptors = getInterceptors()
|
|
42
96
|
if (!updatableInterceptors) return
|
|
43
97
|
|
|
44
98
|
if (Array.isArray(undiciConfig?.interceptors)) {
|
|
@@ -98,11 +152,7 @@ async function loadInterceptors (_require, interceptorsConfigs, key) {
|
|
|
98
152
|
}
|
|
99
153
|
|
|
100
154
|
async function loadInterceptor (_require, interceptorConfig, key) {
|
|
101
|
-
|
|
102
|
-
if (!updatableInterceptors) {
|
|
103
|
-
updatableInterceptors = {}
|
|
104
|
-
globalThis[kInterceptors] = updatableInterceptors
|
|
105
|
-
}
|
|
155
|
+
const updatableInterceptors = getInterceptors()
|
|
106
156
|
|
|
107
157
|
const { module, options } = interceptorConfig
|
|
108
158
|
|
|
@@ -199,24 +249,34 @@ function parseOrigins (origins) {
|
|
|
199
249
|
|
|
200
250
|
function createHttpCacheInterceptor (runtimeConfig) {
|
|
201
251
|
const httpCache = runtimeConfig.httpCache
|
|
252
|
+
|
|
202
253
|
const cacheInterceptor = httpCacheInterceptor({
|
|
203
254
|
store: new RemoteCacheStore({
|
|
204
255
|
onRequest: opts => {
|
|
205
|
-
|
|
256
|
+
const onHttpCacheRequest = getOnHttpCacheRequest({ throwOnMissing: false })
|
|
257
|
+
if (onHttpCacheRequest) {
|
|
258
|
+
onHttpCacheRequest(opts)
|
|
259
|
+
}
|
|
206
260
|
},
|
|
207
261
|
onCacheHit: opts => {
|
|
208
|
-
|
|
262
|
+
const onHttpCacheHit = getOnHttpCacheHit({ throwOnMissing: false })
|
|
263
|
+
if (onHttpCacheHit) {
|
|
264
|
+
onHttpCacheHit(opts)
|
|
265
|
+
}
|
|
209
266
|
},
|
|
210
267
|
onCacheMiss: opts => {
|
|
211
|
-
|
|
268
|
+
const onHttpCacheMiss = getOnHttpCacheMiss({ throwOnMissing: false })
|
|
269
|
+
if (onHttpCacheMiss) {
|
|
270
|
+
onHttpCacheMiss(opts)
|
|
271
|
+
}
|
|
212
272
|
},
|
|
213
|
-
logger:
|
|
273
|
+
logger: getLogger()
|
|
214
274
|
}),
|
|
215
275
|
methods: httpCache.methods ?? ['GET', 'HEAD'],
|
|
216
276
|
origins: parseOrigins(httpCache.origins),
|
|
217
277
|
cacheByDefault: httpCache.cacheByDefault,
|
|
218
278
|
type: httpCache.type,
|
|
219
|
-
logger:
|
|
279
|
+
logger: getLogger()
|
|
220
280
|
})
|
|
221
281
|
return cacheInterceptor
|
|
222
282
|
}
|
package/lib/worker/itc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureLoggableError, executeInParallel, executeWithTimeout, kTimeout } from '@platformatic/foundation'
|
|
2
|
+
import { getEvents, getLogger, getMessaging, updateGlobals } from '@platformatic/globals'
|
|
2
3
|
import { initializeITCTelemetry, ITC } from '@platformatic/itc'
|
|
3
4
|
import { Unpromise } from '@watchable/unpromise'
|
|
4
5
|
import { once } from 'node:events'
|
|
@@ -150,10 +151,10 @@ export async function waitEventFromITC (worker, event) {
|
|
|
150
151
|
export async function setupITC (controller, application, dispatcher, sharedContext) {
|
|
151
152
|
await initializeITCTelemetry()
|
|
152
153
|
|
|
153
|
-
const logger =
|
|
154
|
+
const logger = getLogger()
|
|
154
155
|
const messaging = new MessagingITC(controller.applicationConfig.id, workerData.config, logger)
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
updateGlobals({
|
|
157
158
|
messaging: {
|
|
158
159
|
handle: messaging.handle.bind(messaging),
|
|
159
160
|
send: messaging.send.bind(messaging),
|
|
@@ -172,7 +173,8 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
172
173
|
await once(controller, 'start')
|
|
173
174
|
} else {
|
|
174
175
|
// This gives a chance to a capability to perform custom logic
|
|
175
|
-
|
|
176
|
+
const events = getEvents()
|
|
177
|
+
events.emit('start')
|
|
176
178
|
|
|
177
179
|
try {
|
|
178
180
|
await controller.start()
|
|
@@ -205,7 +207,8 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
205
207
|
|
|
206
208
|
if (force || status.startsWith('start')) {
|
|
207
209
|
// This gives a chance to a capability to perform custom logic
|
|
208
|
-
|
|
210
|
+
const events = getEvents()
|
|
211
|
+
events.emit('stop')
|
|
209
212
|
|
|
210
213
|
await controller.stop(force, dependents)
|
|
211
214
|
}
|
|
@@ -419,9 +422,16 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
419
422
|
// For service-based capabilities, expose the Fastify app
|
|
420
423
|
replServer.context.app = controller.capability?.getApplication?.()
|
|
421
424
|
replServer.context.capability = controller.capability
|
|
422
|
-
replServer.context.platformatic =
|
|
425
|
+
replServer.context.platformatic = {
|
|
426
|
+
capability: controller.capability,
|
|
427
|
+
config: controller.config,
|
|
428
|
+
events: getEvents(),
|
|
429
|
+
itc,
|
|
430
|
+
logger: getLogger(),
|
|
431
|
+
messaging: getMessaging()
|
|
432
|
+
}
|
|
423
433
|
replServer.context.config = controller.applicationConfig
|
|
424
|
-
replServer.context.logger =
|
|
434
|
+
replServer.context.logger = getLogger()
|
|
425
435
|
|
|
426
436
|
replServer.on('exit', () => {
|
|
427
437
|
port.postMessage({ type: 'exit' })
|
package/lib/worker/main.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getPrivateSymbol,
|
|
7
7
|
parseMemorySize
|
|
8
8
|
} from '@platformatic/foundation'
|
|
9
|
+
import { getITC, getLogger, updateGlobals } from '@platformatic/globals'
|
|
9
10
|
import { addPinoInstrumentation } from '@platformatic/telemetry'
|
|
10
11
|
import { Buffer } from 'node:buffer'
|
|
11
12
|
import { subscribe } from 'node:diagnostics_channel'
|
|
@@ -25,11 +26,12 @@ import { initHealthSignalsApi } from './health-signals.js'
|
|
|
25
26
|
import { setDispatcher } from './interceptors.js'
|
|
26
27
|
import { setupITC } from './itc.js'
|
|
27
28
|
import { SharedContext } from './shared-context.js'
|
|
28
|
-
import {
|
|
29
|
+
import { kStderrMarker } from './symbols.js'
|
|
29
30
|
|
|
30
31
|
function handlePreInitializationUnhandled (type, err) {
|
|
31
32
|
const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
|
|
32
|
-
|
|
33
|
+
const logger = getLogger()
|
|
34
|
+
logger.error(
|
|
33
35
|
{ err: ensureLoggableError(err) },
|
|
34
36
|
`The ${label} threw an ${type} before initialization.`
|
|
35
37
|
)
|
|
@@ -38,7 +40,8 @@ function handlePreInitializationUnhandled (type, err) {
|
|
|
38
40
|
|
|
39
41
|
class ForwardingEventEmitter extends EventEmitter {
|
|
40
42
|
emitAndNotify (event, ...args) {
|
|
41
|
-
|
|
43
|
+
const itc = getITC()
|
|
44
|
+
itc.notify('event', { event, payload: args })
|
|
42
45
|
return this.emit(event, ...args)
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -201,8 +204,9 @@ async function main () {
|
|
|
201
204
|
const cleanup = setupHandlers()
|
|
202
205
|
|
|
203
206
|
installUndiciGlobals(globalThis)
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
updateGlobals({
|
|
208
|
+
runtimeId: threadId,
|
|
209
|
+
interceptors: {},
|
|
206
210
|
logger: createLogger(),
|
|
207
211
|
events: new ForwardingEventEmitter()
|
|
208
212
|
})
|
|
@@ -210,11 +214,11 @@ async function main () {
|
|
|
210
214
|
const runtimeConfig = workerData.config
|
|
211
215
|
const applicationConfig = workerData.applicationConfig
|
|
212
216
|
|
|
213
|
-
setupBufferPool(runtimeConfig, applicationConfig,
|
|
214
|
-
setupDefaultHighWaterMark(runtimeConfig, applicationConfig,
|
|
217
|
+
setupBufferPool(runtimeConfig, applicationConfig, getLogger())
|
|
218
|
+
setupDefaultHighWaterMark(runtimeConfig, applicationConfig, getLogger())
|
|
215
219
|
|
|
216
220
|
// Enable compile cache early before loading user modules
|
|
217
|
-
await setupCompileCache(runtimeConfig, applicationConfig,
|
|
221
|
+
await setupCompileCache(runtimeConfig, applicationConfig, getLogger())
|
|
218
222
|
|
|
219
223
|
await performPreloading(runtimeConfig, applicationConfig)
|
|
220
224
|
|
|
@@ -226,7 +230,8 @@ async function main () {
|
|
|
226
230
|
envfile = resolve(workerData.applicationConfig.path, '.env')
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
const logger = getLogger()
|
|
234
|
+
logger.debug({ envfile }, 'Loading envfile...')
|
|
230
235
|
|
|
231
236
|
try {
|
|
232
237
|
process.loadEnvFile(envfile)
|
|
@@ -301,15 +306,16 @@ async function main () {
|
|
|
301
306
|
|
|
302
307
|
const sharedContext = new SharedContext()
|
|
303
308
|
// Limit the amount of methods a user can call
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
309
|
+
updateGlobals({
|
|
310
|
+
sharedContext: {
|
|
311
|
+
get: () => sharedContext.get(),
|
|
312
|
+
update: (...args) => sharedContext.update(...args)
|
|
313
|
+
}
|
|
314
|
+
})
|
|
308
315
|
|
|
309
316
|
// Setup interaction with parent port
|
|
310
317
|
const itc = await setupITC(controller, applicationConfig, threadDispatcher, sharedContext)
|
|
311
|
-
|
|
312
|
-
globalThis.platformatic.itc = itc
|
|
318
|
+
updateGlobals({ itc })
|
|
313
319
|
|
|
314
320
|
// Setup management client for privileged applications
|
|
315
321
|
if (applicationConfig.management) {
|
|
@@ -322,7 +328,7 @@ async function main () {
|
|
|
322
328
|
const ops = typeof applicationConfig.management === 'object'
|
|
323
329
|
? applicationConfig.management.operations
|
|
324
330
|
: undefined
|
|
325
|
-
|
|
331
|
+
updateGlobals({ management: new ManagementClient(ops) })
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
|
package/lib/worker/management.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getITC } from '@platformatic/globals'
|
|
2
2
|
|
|
3
3
|
export class ManagementClient {
|
|
4
4
|
#allowedOperations
|
|
@@ -14,7 +14,8 @@ export class ManagementClient {
|
|
|
14
14
|
throw new Error(`Operation "${operation}" is not allowed`)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const itc = getITC()
|
|
18
|
+
return itc.send('management:' + operation, data)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
getRuntimeStatus () {
|
package/lib/worker/messaging.js
CHANGED
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
startOutgoingMessagingSpanSync,
|
|
11
11
|
traceIncomingMessagingHandler
|
|
12
12
|
} from '@platformatic/itc'
|
|
13
|
+
import { getITC } from '@platformatic/globals'
|
|
13
14
|
import { MessagingError } from '../errors.js'
|
|
14
15
|
import { RoundRobinMap } from './round-robin-map.js'
|
|
15
|
-
import {
|
|
16
|
+
import { kWorkersBroadcast } from './symbols.js'
|
|
16
17
|
|
|
17
18
|
const kPendingResponses = Symbol('plt.messaging.pendingResponses')
|
|
18
19
|
|
|
@@ -76,7 +77,7 @@ export class MessagingITC extends ITC {
|
|
|
76
77
|
|
|
77
78
|
try {
|
|
78
79
|
// Get the next worker for the application
|
|
79
|
-
const worker = this
|
|
80
|
+
const worker = this._getNextWorker(application)
|
|
80
81
|
|
|
81
82
|
if (!worker) {
|
|
82
83
|
throw new MessagingError(application, 'No workers available')
|
|
@@ -85,8 +86,9 @@ export class MessagingITC extends ITC {
|
|
|
85
86
|
if (!worker.channel) {
|
|
86
87
|
// Use twice the value here as a fallback measure. The target handler in the main thread is forwarding
|
|
87
88
|
// the request to the worker, using executeWithTimeout with the user set timeout value.
|
|
89
|
+
const itc = getITC()
|
|
88
90
|
const channel = await executeWithTimeout(
|
|
89
|
-
|
|
91
|
+
itc.send('getWorkerMessagingChannel', {
|
|
90
92
|
id: this.#id,
|
|
91
93
|
application: worker.application,
|
|
92
94
|
worker: worker.worker
|
|
@@ -100,10 +102,7 @@ export class MessagingITC extends ITC {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
worker.channel = channel
|
|
103
|
-
this
|
|
104
|
-
|
|
105
|
-
channel[kPendingResponses] = new Map()
|
|
106
|
-
channel.on('close', this.#handlePendingResponse.bind(this, channel))
|
|
105
|
+
this._setupChannel(channel)
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
const context = { ...options }
|
|
@@ -164,13 +163,11 @@ export class MessagingITC extends ITC {
|
|
|
164
163
|
|
|
165
164
|
async addSource (channel) {
|
|
166
165
|
this.#sources.add(channel)
|
|
167
|
-
this
|
|
166
|
+
this._setupChannel(channel)
|
|
167
|
+
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
channel.on('close', () => {
|
|
172
|
-
this.#sources.delete(channel)
|
|
173
|
-
})
|
|
169
|
+
_getNextWorker (application) {
|
|
170
|
+
return this.#workers.next(application)
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
_send (request, context) {
|
|
@@ -181,7 +178,7 @@ export class MessagingITC extends ITC {
|
|
|
181
178
|
channel[kPendingResponses].set(request.reqId, { application, request })
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
channel.postMessage(sanitize(request, transferList),
|
|
181
|
+
channel.postMessage(sanitize(request, transferList), transferList)
|
|
185
182
|
}
|
|
186
183
|
|
|
187
184
|
_createClosePromise () {
|
|
@@ -209,11 +206,22 @@ export class MessagingITC extends ITC {
|
|
|
209
206
|
this.#sources.clear()
|
|
210
207
|
}
|
|
211
208
|
|
|
212
|
-
|
|
209
|
+
_setupChannel (channel) {
|
|
210
|
+
if (channel[kPendingResponses]) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
channel[kPendingResponses] = new Map()
|
|
215
|
+
|
|
213
216
|
// Setup the message for processing
|
|
214
217
|
channel.on('message', event => {
|
|
215
218
|
this.#listener(event, { channel, message: event })
|
|
216
219
|
})
|
|
220
|
+
|
|
221
|
+
channel.on('close', () => {
|
|
222
|
+
this.#sources.delete(channel)
|
|
223
|
+
this.#handlePendingResponse(channel)
|
|
224
|
+
})
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
#updateWorkers (event) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getITC } from '@platformatic/globals'
|
|
2
2
|
|
|
3
3
|
export class SharedContext {
|
|
4
4
|
constructor () {
|
|
@@ -6,12 +6,14 @@ export class SharedContext {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
update (context, options = {}) {
|
|
9
|
-
|
|
9
|
+
const itc = getITC()
|
|
10
|
+
return itc.send('updateSharedContext', { ...options, context })
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
get () {
|
|
13
14
|
if (this.sharedContext === null) {
|
|
14
|
-
|
|
15
|
+
const itc = getITC()
|
|
16
|
+
this.sharedContext = itc.send('getSharedContext')
|
|
15
17
|
}
|
|
16
18
|
return this.sharedContext
|
|
17
19
|
}
|
package/lib/worker/symbols.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const kConfig = Symbol.for('plt.runtime.config')
|
|
2
|
-
export const kId = Symbol.for('plt.runtime.id')
|
|
2
|
+
export const kId = Symbol.for('plt.runtime.id')
|
|
3
3
|
export const kFullId = Symbol.for('plt.runtime.fullId')
|
|
4
4
|
export const kApplicationId = Symbol.for('plt.runtime.application.id')
|
|
5
5
|
export const kWorkerId = Symbol.for('plt.runtime.worker.id')
|
|
@@ -8,7 +8,6 @@ export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer
|
|
|
8
8
|
export const kWorkerStatus = Symbol('plt.runtime.worker.status')
|
|
9
9
|
export const kWorkerHealthSignals = Symbol.for('plt.runtime.worker.healthSignals')
|
|
10
10
|
export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
|
|
11
|
-
export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
|
|
12
11
|
export const kLastHealthCheckELU = Symbol.for('plt.runtime.worker.lastHealthCheckELU')
|
|
13
12
|
export const kLastWorkerScalerELU = Symbol.for('plt.runtime.worker.lastWorkerScalerELU')
|
|
14
13
|
export const kInterceptorReadyPromise = Symbol.for('plt.runtime.worker.interceptorReadyPromise')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.56.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"typescript": "^5.5.4",
|
|
37
37
|
"undici-oidc-interceptor": "^0.5.0",
|
|
38
38
|
"why-is-node-running": "^2.2.2",
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/sql-graphql": "3.
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/
|
|
46
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
39
|
+
"@platformatic/composer": "3.56.0",
|
|
40
|
+
"@platformatic/db": "3.56.0",
|
|
41
|
+
"@platformatic/service": "3.56.0",
|
|
42
|
+
"@platformatic/gateway": "3.56.0",
|
|
43
|
+
"@platformatic/sql-graphql": "3.56.0",
|
|
44
|
+
"@platformatic/sql-mapper": "3.56.0",
|
|
45
|
+
"@platformatic/node": "3.56.0",
|
|
46
|
+
"@platformatic/wattpm-pprof-capture": "3.56.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -70,15 +70,16 @@
|
|
|
70
70
|
"semgrator": "^0.3.0",
|
|
71
71
|
"sonic-boom": "^4.2.0",
|
|
72
72
|
"systeminformation": "^5.27.11",
|
|
73
|
-
"undici": "^7.
|
|
73
|
+
"undici": "^7.27.2",
|
|
74
74
|
"undici-thread-interceptor": "^1.3.1",
|
|
75
75
|
"ws": "^8.16.0",
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/
|
|
80
|
-
"@platformatic/
|
|
81
|
-
"@platformatic/telemetry": "3.
|
|
76
|
+
"@platformatic/basic": "3.56.0",
|
|
77
|
+
"@platformatic/generators": "3.56.0",
|
|
78
|
+
"@platformatic/globals": "3.56.0",
|
|
79
|
+
"@platformatic/metrics": "3.56.0",
|
|
80
|
+
"@platformatic/itc": "3.56.0",
|
|
81
|
+
"@platformatic/telemetry": "3.56.0",
|
|
82
|
+
"@platformatic/foundation": "3.56.0"
|
|
82
83
|
},
|
|
83
84
|
"engines": {
|
|
84
85
|
"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.56.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|