@platformatic/runtime 3.63.0-alpha.3 → 3.64.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/config.d.ts +4 -0
- package/index.d.ts +179 -8
- package/lib/errors.js +30 -0
- package/lib/management-api.js +16 -3
- package/lib/management-handlers.js +3 -3
- package/lib/prom-server.js +81 -8
- package/lib/runtime.js +664 -57
- package/lib/scaling-algorithm.js +3 -3
- package/lib/worker/controller.js +4 -2
- package/lib/worker/itc.js +37 -18
- package/lib/worker/main.js +14 -4
- package/lib/worker/management.js +2 -2
- package/lib/worker/messaging.js +20 -7
- package/lib/worker-scaler.js +20 -9
- package/package.json +17 -17
- package/schema.json +13 -2
package/lib/scaling-algorithm.js
CHANGED
|
@@ -83,7 +83,7 @@ export class ScalingAlgorithm {
|
|
|
83
83
|
|
|
84
84
|
const newWorkersCount = appMinWorkers - workersCount
|
|
85
85
|
totalWorkersCount += newWorkersCount
|
|
86
|
-
totalAvailableMemory
|
|
86
|
+
totalAvailableMemory -= newWorkersCount * avgHeapUsed
|
|
87
87
|
continue
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -96,7 +96,7 @@ export class ScalingAlgorithm {
|
|
|
96
96
|
|
|
97
97
|
const removedWorkersCount = workersCount - appMaxWorkers
|
|
98
98
|
totalWorkersCount -= removedWorkersCount
|
|
99
|
-
totalAvailableMemory
|
|
99
|
+
totalAvailableMemory += removedWorkersCount * avgHeapUsed
|
|
100
100
|
continue
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -111,7 +111,7 @@ export class ScalingAlgorithm {
|
|
|
111
111
|
|
|
112
112
|
const removedWorkersCount = 1
|
|
113
113
|
totalWorkersCount -= removedWorkersCount
|
|
114
|
-
totalAvailableMemory
|
|
114
|
+
totalAvailableMemory += removedWorkersCount * avgHeapUsed
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
package/lib/worker/controller.js
CHANGED
|
@@ -95,7 +95,8 @@ export class Controller extends EventEmitter {
|
|
|
95
95
|
worker: workerData?.worker,
|
|
96
96
|
resourceLimits: workerData?.resourceLimits,
|
|
97
97
|
hasManagementApi: !!runtimeConfig.managementApi,
|
|
98
|
-
fetchApplicationUrl: fetchApplicationUrl.bind(null, applicationConfig)
|
|
98
|
+
fetchApplicationUrl: fetchApplicationUrl.bind(null, applicationConfig),
|
|
99
|
+
strictEnv: runtimeConfig.strictEnv
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
@@ -141,7 +142,8 @@ export class Controller extends EventEmitter {
|
|
|
141
142
|
if (appConfig.config) {
|
|
142
143
|
// Parse the configuration file the first time to obtain the schema
|
|
143
144
|
const unvalidatedConfig = await loadConfiguration(appConfig.config, null, {
|
|
144
|
-
onMissingEnv: this.#context.fetchApplicationUrl
|
|
145
|
+
onMissingEnv: this.#context.fetchApplicationUrl,
|
|
146
|
+
strictEnv: this.#context.strictEnv
|
|
145
147
|
})
|
|
146
148
|
const pkg = await loadConfigurationModule(appConfig.path, unvalidatedConfig)
|
|
147
149
|
this.capability = await pkg.create(appConfig.path, appConfig.config, this.#context)
|
package/lib/worker/itc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ensureLoggableError, executeInParallel, executeWithTimeout, kTimeout } from '@platformatic/foundation'
|
|
2
2
|
import { getEvents, getLogger, getMessaging, updateGlobals } from '@platformatic/globals'
|
|
3
|
-
import {
|
|
3
|
+
import { ITC, initializeITCTelemetry } from '@platformatic/itc'
|
|
4
4
|
import { Unpromise } from '@watchable/unpromise'
|
|
5
5
|
import { once } from 'node:events'
|
|
6
6
|
import { createRequire } from 'node:module'
|
|
@@ -112,9 +112,14 @@ async function safeHandleInITC (worker, fn) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
async function closeITC (dispatcher, itc, messaging) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
try {
|
|
116
|
+
await dispatcher.interceptor.close()
|
|
117
|
+
itc.close()
|
|
118
|
+
messaging.close()
|
|
119
|
+
} finally {
|
|
120
|
+
const events = getEvents()
|
|
121
|
+
events.emit('exit')
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export async function sendViaITC (worker, name, message, transferList) {
|
|
@@ -183,7 +188,12 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
183
188
|
|
|
184
189
|
// Reply to the runtime that the start failed, so it can cleanup
|
|
185
190
|
once(itc, 'application:worker:start:processed').then(() => {
|
|
186
|
-
closeITC(dispatcher, itc, messaging).catch(
|
|
191
|
+
closeITC(dispatcher, itc, messaging).catch(err => {
|
|
192
|
+
logger.error(
|
|
193
|
+
{ err: ensureLoggableError(err) },
|
|
194
|
+
'Failed to close the worker ITC after a failed start.'
|
|
195
|
+
)
|
|
196
|
+
})
|
|
187
197
|
})
|
|
188
198
|
|
|
189
199
|
throw ensureLoggableError(e)
|
|
@@ -208,23 +218,32 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
208
218
|
},
|
|
209
219
|
|
|
210
220
|
async stop ({ force, dependents }) {
|
|
211
|
-
|
|
221
|
+
try {
|
|
222
|
+
const status = controller.getStatus()
|
|
212
223
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
if (!force && status === 'starting') {
|
|
225
|
+
await once(controller, 'start')
|
|
226
|
+
}
|
|
216
227
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
if (force || status.startsWith('start')) {
|
|
229
|
+
// This gives a chance to a capability to perform custom logic
|
|
230
|
+
const events = getEvents()
|
|
231
|
+
events.emit('stop')
|
|
221
232
|
|
|
222
|
-
|
|
233
|
+
await controller.stop(force, dependents)
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
// Always schedule cleanup, even when stop throws. Otherwise the worker
|
|
237
|
+
// keeps open handles and runtime shutdown hangs until the grace timeout.
|
|
238
|
+
once(itc, 'application:worker:stop:processed').then(() => {
|
|
239
|
+
closeITC(dispatcher, itc, messaging).catch(err => {
|
|
240
|
+
logger.error(
|
|
241
|
+
{ err: ensureLoggableError(err) },
|
|
242
|
+
'Failed to close the worker ITC after stop.'
|
|
243
|
+
)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
223
246
|
}
|
|
224
|
-
|
|
225
|
-
once(itc, 'application:worker:stop:processed').then(() => {
|
|
226
|
-
closeITC(dispatcher, itc, messaging).catch(() => {})
|
|
227
|
-
})
|
|
228
247
|
},
|
|
229
248
|
|
|
230
249
|
async getDependencies () {
|
package/lib/worker/main.js
CHANGED
|
@@ -364,6 +364,16 @@ async function main () {
|
|
|
364
364
|
|
|
365
365
|
function stripBasePath (basePath) {
|
|
366
366
|
const kBasePath = Symbol('kBasePath')
|
|
367
|
+
const absoluteUrlPattern = /^https?:\/\//i
|
|
368
|
+
|
|
369
|
+
function prependBasePath (value) {
|
|
370
|
+
// Absolute and protocol-relative URLs (RFC 7231 Location) must not be rewritten
|
|
371
|
+
if (typeof value !== 'string' || absoluteUrlPattern.test(value) || value.startsWith('//') || value.startsWith(basePath)) {
|
|
372
|
+
return value
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return basePath + value
|
|
376
|
+
}
|
|
367
377
|
|
|
368
378
|
subscribe('http.server.request.start', ({ request, response }) => {
|
|
369
379
|
if (request.url.startsWith(basePath)) {
|
|
@@ -389,8 +399,8 @@ function stripBasePath (basePath) {
|
|
|
389
399
|
|
|
390
400
|
if (headers) {
|
|
391
401
|
for (const key in headers) {
|
|
392
|
-
if (key.toLowerCase() === 'location'
|
|
393
|
-
headers[key] =
|
|
402
|
+
if (key.toLowerCase() === 'location') {
|
|
403
|
+
headers[key] = prependBasePath(headers[key])
|
|
394
404
|
}
|
|
395
405
|
}
|
|
396
406
|
}
|
|
@@ -401,8 +411,8 @@ function stripBasePath (basePath) {
|
|
|
401
411
|
|
|
402
412
|
ServerResponse.prototype.setHeader = function (name, value) {
|
|
403
413
|
if (this[kBasePath]) {
|
|
404
|
-
if (name.toLowerCase() === 'location'
|
|
405
|
-
value =
|
|
414
|
+
if (name.toLowerCase() === 'location') {
|
|
415
|
+
value = prependBasePath(value)
|
|
406
416
|
}
|
|
407
417
|
}
|
|
408
418
|
originSetHeader.call(this, name, value)
|
package/lib/worker/management.js
CHANGED
package/lib/worker/messaging.js
CHANGED
|
@@ -114,14 +114,21 @@ export class MessagingITC extends ITC {
|
|
|
114
114
|
context.meta = { ...options?.meta, ...telemetry?.meta }
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
const abortController = new AbortController()
|
|
118
|
+
const timeout = setTimeout(() => abortController.abort(), this.#timeout).unref()
|
|
119
|
+
context.signal = abortController.signal
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const send = () => super.send(name, message, context)
|
|
123
|
+
return telemetry ? await telemetry.run(send) : await send()
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (abortController.signal.aborted) {
|
|
126
|
+
throw new MessagingError(application, 'Timeout while waiting for a response.')
|
|
127
|
+
}
|
|
128
|
+
throw err
|
|
129
|
+
} finally {
|
|
130
|
+
clearTimeout(timeout)
|
|
122
131
|
}
|
|
123
|
-
|
|
124
|
-
return response
|
|
125
132
|
} catch (err) {
|
|
126
133
|
error = err
|
|
127
134
|
throw err
|
|
@@ -181,6 +188,12 @@ export class MessagingITC extends ITC {
|
|
|
181
188
|
channel.postMessage(sanitize(request, transferList), transferList)
|
|
182
189
|
}
|
|
183
190
|
|
|
191
|
+
_onRequestSettled (reqId, context) {
|
|
192
|
+
if (context.trackResponse) {
|
|
193
|
+
context.channel[kPendingResponses].delete(reqId)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
_createClosePromise () {
|
|
185
198
|
const { promise, resolve, reject } = Promise.withResolvers()
|
|
186
199
|
this.#closeResolvers = { resolve, reject }
|
package/lib/worker-scaler.js
CHANGED
|
@@ -121,20 +121,31 @@ export class DynamicWorkersScaler {
|
|
|
121
121
|
|
|
122
122
|
if (config.minWorkers > 1) {
|
|
123
123
|
const update = { application: application.id, workers: config.minWorkers }
|
|
124
|
-
|
|
125
|
-
if (!this.#status === 'started') {
|
|
126
|
-
await this.#runtime.updateApplicationsResources([update])
|
|
127
|
-
} else {
|
|
128
|
-
this.#initialUpdates.push(update)
|
|
129
|
-
}
|
|
124
|
+
this.#initialUpdates.push(update)
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
this.#appsConfigs[application.id] = config
|
|
133
128
|
this.#algorithm.addApplication(application.id, config)
|
|
134
129
|
}
|
|
135
130
|
|
|
136
|
-
|
|
137
|
-
this.#
|
|
131
|
+
async applyPendingUpdate (applicationId) {
|
|
132
|
+
if (this.#status !== 'started') {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const update = this.#initialUpdates.find(update => update.application === applicationId)
|
|
137
|
+
if (!update) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await this.#runtime.updateApplicationsResources([update])
|
|
142
|
+
this.#initialUpdates = this.#initialUpdates.filter(pending => pending !== update)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
remove (applicationId) {
|
|
146
|
+
delete this.#appsConfigs[applicationId]
|
|
147
|
+
this.#initialUpdates = this.#initialUpdates.filter(update => update.application !== applicationId)
|
|
148
|
+
this.#algorithm.removeApplication(applicationId)
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
async #chechHealth () {
|
|
@@ -175,7 +186,7 @@ export class DynamicWorkersScaler {
|
|
|
175
186
|
shouldCheckForScaling = true
|
|
176
187
|
}
|
|
177
188
|
} catch (err) {
|
|
178
|
-
this.logger.error({ err }, 'Failed to get health for worker')
|
|
189
|
+
this.#runtime.logger.error({ err }, 'Failed to get health for worker')
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.64.0",
|
|
4
4
|
"description": "Platformatic runtime",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
40
|
"undici-oidc-interceptor": "^0.9.0",
|
|
41
41
|
"why-is-node-running": "^2.2.2",
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/
|
|
44
|
-
"@platformatic/gateway": "3.
|
|
45
|
-
"@platformatic/
|
|
46
|
-
"@platformatic/
|
|
47
|
-
"@platformatic/sql-graphql": "3.
|
|
48
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
49
|
-
"@platformatic/sql-mapper": "3.
|
|
42
|
+
"@platformatic/db": "3.64.0",
|
|
43
|
+
"@platformatic/composer": "3.64.0",
|
|
44
|
+
"@platformatic/gateway": "3.64.0",
|
|
45
|
+
"@platformatic/service": "3.64.0",
|
|
46
|
+
"@platformatic/node": "3.64.0",
|
|
47
|
+
"@platformatic/sql-graphql": "3.64.0",
|
|
48
|
+
"@platformatic/wattpm-pprof-capture": "3.64.0",
|
|
49
|
+
"@platformatic/sql-mapper": "3.64.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"help-me": "^5.0.0",
|
|
70
70
|
"minimist": "^1.2.8",
|
|
71
71
|
"pino": "^10.1.0",
|
|
72
|
-
"pino-opentelemetry-transport": "^
|
|
72
|
+
"pino-opentelemetry-transport": "^4.0.2",
|
|
73
73
|
"pino-pretty": "^13.0.0",
|
|
74
74
|
"semgrator": "^0.3.0",
|
|
75
75
|
"sonic-boom": "^4.2.0",
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
"undici": "^7.27.2",
|
|
78
78
|
"undici-thread-interceptor": "^1.3.1",
|
|
79
79
|
"ws": "^8.16.0",
|
|
80
|
-
"@platformatic/
|
|
81
|
-
"@platformatic/
|
|
82
|
-
"@platformatic/generators": "3.
|
|
83
|
-
"@platformatic/itc": "3.
|
|
84
|
-
"@platformatic/
|
|
85
|
-
"@platformatic/
|
|
86
|
-
"@platformatic/telemetry": "3.
|
|
80
|
+
"@platformatic/foundation": "3.64.0",
|
|
81
|
+
"@platformatic/globals": "3.64.0",
|
|
82
|
+
"@platformatic/generators": "3.64.0",
|
|
83
|
+
"@platformatic/itc": "3.64.0",
|
|
84
|
+
"@platformatic/basic": "3.64.0",
|
|
85
|
+
"@platformatic/metrics": "3.64.0",
|
|
86
|
+
"@platformatic/telemetry": "3.64.0"
|
|
87
87
|
},
|
|
88
88
|
"engines": {
|
|
89
89
|
"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.64.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -2234,7 +2234,7 @@
|
|
|
2234
2234
|
"type": "string"
|
|
2235
2235
|
}
|
|
2236
2236
|
],
|
|
2237
|
-
"default":
|
|
2237
|
+
"default": 30000
|
|
2238
2238
|
},
|
|
2239
2239
|
"application": {
|
|
2240
2240
|
"anyOf": [
|
|
@@ -3492,6 +3492,17 @@
|
|
|
3492
3492
|
"envfile": {
|
|
3493
3493
|
"type": "string"
|
|
3494
3494
|
},
|
|
3495
|
+
"strictEnv": {
|
|
3496
|
+
"anyOf": [
|
|
3497
|
+
{
|
|
3498
|
+
"type": "boolean"
|
|
3499
|
+
},
|
|
3500
|
+
{
|
|
3501
|
+
"type": "string"
|
|
3502
|
+
}
|
|
3503
|
+
],
|
|
3504
|
+
"description": "When set to true, the configuration loading fails if a {PLT_*} placeholder references an environment variable which is not set. When set to \"warn\", a warning listing the missing variables is logged but the placeholders are still replaced with an empty string. Defaults to false."
|
|
3505
|
+
},
|
|
3495
3506
|
"sourceMaps": {
|
|
3496
3507
|
"type": "boolean",
|
|
3497
3508
|
"default": false
|