@platformatic/runtime 3.5.1 → 3.7.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 +1 -0
- package/index.js +5 -1
- package/lib/errors.js +1 -1
- package/lib/generator.js +4 -4
- package/lib/management-api.js +4 -3
- package/lib/runtime.js +79 -41
- package/lib/worker/itc.js +4 -2
- package/lib/worker/main.js +9 -1
- package/lib/worker/messaging.js +50 -3
- package/package.json +16 -16
- package/schema.json +13 -1
package/config.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -36,9 +36,13 @@ function handleSignal (runtime, config) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function onTimeout (timeout) {
|
|
40
|
+
runtime.logger.error(`Could not close the runtime in ${timeout} ms, aborting the process with exit code 1.`)
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
process.on('newListener', filterCloseWithGraceSIGUSR2)
|
|
40
44
|
|
|
41
|
-
const cwg = closeWithGrace({ delay: config.gracefulShutdown?.runtime ?? 10000 }, async event => {
|
|
45
|
+
const cwg = closeWithGrace({ delay: config.gracefulShutdown?.runtime ?? 10000, onTimeout }, async event => {
|
|
42
46
|
if (event.err instanceof Error) {
|
|
43
47
|
console.error(event.err)
|
|
44
48
|
}
|
package/lib/errors.js
CHANGED
|
@@ -105,7 +105,7 @@ export const CannotMapSpecifierToAbsolutePathError = createError(
|
|
|
105
105
|
)
|
|
106
106
|
export const NodeInspectorFlagsNotSupportedError = createError(
|
|
107
107
|
`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`,
|
|
108
|
-
"The Node.js inspector flags are not supported. Please use '
|
|
108
|
+
"The Node.js inspector flags are not supported. Please use 'wattpm start --inspect' instead."
|
|
109
109
|
)
|
|
110
110
|
export const FailedToUnlinkManagementApiSocket = createError(
|
|
111
111
|
`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`,
|
package/lib/generator.js
CHANGED
|
@@ -103,9 +103,9 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
103
103
|
const template = {
|
|
104
104
|
name: `${this.runtimeName}`,
|
|
105
105
|
scripts: {
|
|
106
|
-
dev: this.config.devCommand,
|
|
107
|
-
build: this.config.buildCommand,
|
|
108
|
-
start: this.config.startCommand ?? '
|
|
106
|
+
dev: this.config.devCommand ?? 'wattpm dev',
|
|
107
|
+
build: this.config.buildCommand ?? 'wattpm build',
|
|
108
|
+
start: this.config.startCommand ?? 'wattpm start'
|
|
109
109
|
},
|
|
110
110
|
devDependencies: {
|
|
111
111
|
fastify: `^${this.fastifyVersion}`
|
|
@@ -594,7 +594,7 @@ export class WrappedGenerator extends BaseGenerator {
|
|
|
594
594
|
scripts ??= {}
|
|
595
595
|
scripts.dev ??= this.config.devCommand
|
|
596
596
|
scripts.build ??= this.config.buildCommand
|
|
597
|
-
scripts.start ??= this.config.startCommand ?? '
|
|
597
|
+
scripts.start ??= this.config.startCommand ?? 'wattpm start'
|
|
598
598
|
|
|
599
599
|
this.addFile({
|
|
600
600
|
path: '',
|
package/lib/management-api.js
CHANGED
|
@@ -36,9 +36,10 @@ export async function managementApiPlugin (app, opts) {
|
|
|
36
36
|
await runtime.close()
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
app.post('/restart', async
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
app.post('/restart', async request => {
|
|
40
|
+
const applications = request.body?.applications ?? []
|
|
41
|
+
app.log.debug({ applications }, 'restart applications')
|
|
42
|
+
await runtime.restart(applications)
|
|
42
43
|
})
|
|
43
44
|
|
|
44
45
|
app.get('/applications', async () => {
|
package/lib/runtime.js
CHANGED
|
@@ -337,14 +337,19 @@ export class Runtime extends EventEmitter {
|
|
|
337
337
|
this.#updateStatus('stopped')
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
async restart () {
|
|
341
|
-
this.
|
|
340
|
+
async restart (applications = []) {
|
|
341
|
+
this.emitAndNotify('restarting')
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
this
|
|
345
|
-
|
|
343
|
+
const restartInvocations = []
|
|
344
|
+
for (const application of this.getApplicationsIds()) {
|
|
345
|
+
if (applications.length === 0 || applications.includes(application)) {
|
|
346
|
+
restartInvocations.push([application])
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await executeInParallel(this.restartApplication.bind(this), restartInvocations, this.#concurrency)
|
|
346
351
|
|
|
347
|
-
this.
|
|
352
|
+
this.emitAndNotify('restarted')
|
|
348
353
|
|
|
349
354
|
return this.#url
|
|
350
355
|
}
|
|
@@ -362,6 +367,10 @@ export class Runtime extends EventEmitter {
|
|
|
362
367
|
await this.#prometheusServer.close()
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
if (this.#sharedHttpCache?.close) {
|
|
371
|
+
await this.#sharedHttpCache.close()
|
|
372
|
+
}
|
|
373
|
+
|
|
365
374
|
if (this.logger) {
|
|
366
375
|
this.#loggerDestination?.end()
|
|
367
376
|
|
|
@@ -369,10 +378,6 @@ export class Runtime extends EventEmitter {
|
|
|
369
378
|
this.#loggerDestination = null
|
|
370
379
|
}
|
|
371
380
|
|
|
372
|
-
if (this.#sharedHttpCache?.close) {
|
|
373
|
-
await this.#sharedHttpCache.close()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
381
|
this.#updateStatus('closed')
|
|
377
382
|
}
|
|
378
383
|
|
|
@@ -432,13 +437,13 @@ export class Runtime extends EventEmitter {
|
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
|
|
435
|
-
|
|
440
|
+
emitAndNotify (event, ...payload) {
|
|
436
441
|
for (const worker of this.#workers.values()) {
|
|
437
442
|
worker[kITC].notify('runtime:event', { event, payload })
|
|
438
443
|
}
|
|
439
444
|
|
|
440
445
|
this.logger.trace({ event, payload }, 'Runtime event')
|
|
441
|
-
return
|
|
446
|
+
return this.emit(event, ...payload)
|
|
442
447
|
}
|
|
443
448
|
|
|
444
449
|
async sendCommandToApplication (id, name, message) {
|
|
@@ -473,13 +478,13 @@ export class Runtime extends EventEmitter {
|
|
|
473
478
|
|
|
474
479
|
const workersCount = await this.#workers.getCount(applicationConfig.id)
|
|
475
480
|
|
|
476
|
-
this.
|
|
481
|
+
this.emitAndNotify('application:starting', id)
|
|
477
482
|
|
|
478
483
|
for (let i = 0; i < workersCount; i++) {
|
|
479
484
|
await this.#startWorker(config, applicationConfig, workersCount, id, i, silent)
|
|
480
485
|
}
|
|
481
486
|
|
|
482
|
-
this.
|
|
487
|
+
this.emitAndNotify('application:started', id)
|
|
483
488
|
}
|
|
484
489
|
|
|
485
490
|
async stopApplication (id, silent = false, dependents = []) {
|
|
@@ -492,7 +497,7 @@ export class Runtime extends EventEmitter {
|
|
|
492
497
|
|
|
493
498
|
const workersCount = await this.#workers.getCount(applicationConfig.id)
|
|
494
499
|
|
|
495
|
-
this.
|
|
500
|
+
this.emitAndNotify('application:stopping', id)
|
|
496
501
|
|
|
497
502
|
if (typeof workersCount === 'number') {
|
|
498
503
|
const stopInvocations = []
|
|
@@ -503,16 +508,37 @@ export class Runtime extends EventEmitter {
|
|
|
503
508
|
await executeInParallel(this.#stopWorker.bind(this), stopInvocations, this.#concurrency)
|
|
504
509
|
}
|
|
505
510
|
|
|
506
|
-
this.
|
|
511
|
+
this.emitAndNotify('application:stopped', id)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async restartApplication (id) {
|
|
515
|
+
const config = this.#config
|
|
516
|
+
const applicationConfig = this.#config.applications.find(s => s.id === id)
|
|
517
|
+
const workersCount = await this.#workers.getCount(id)
|
|
518
|
+
|
|
519
|
+
this.emitAndNotify('application:restarting', id)
|
|
520
|
+
|
|
521
|
+
for (let i = 0; i < workersCount; i++) {
|
|
522
|
+
const label = `${id}:${i}`
|
|
523
|
+
const worker = this.#workers.get(label)
|
|
524
|
+
|
|
525
|
+
if (i > 0 && config.workersRestartDelay > 0) {
|
|
526
|
+
await sleep(config.workersRestartDelay)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await this.#replaceWorker(config, applicationConfig, workersCount, id, i, worker, true)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
this.emitAndNotify('application:restarted', id)
|
|
507
533
|
}
|
|
508
534
|
|
|
509
535
|
async buildApplication (id) {
|
|
510
536
|
const application = await this.#getApplicationById(id)
|
|
511
537
|
|
|
512
|
-
this.
|
|
538
|
+
this.emitAndNotify('application:building', id)
|
|
513
539
|
try {
|
|
514
540
|
await sendViaITC(application, 'build')
|
|
515
|
-
this.
|
|
541
|
+
this.emitAndNotify('application:built', id)
|
|
516
542
|
} catch (e) {
|
|
517
543
|
// The application exports no meta, return an empty object
|
|
518
544
|
if (e.code === 'PLT_ITC_HANDLER_NOT_FOUND') {
|
|
@@ -570,7 +596,7 @@ export class Runtime extends EventEmitter {
|
|
|
570
596
|
return
|
|
571
597
|
}
|
|
572
598
|
|
|
573
|
-
this.
|
|
599
|
+
this.emitAndNotify('metrics', metrics)
|
|
574
600
|
this.#metrics.push(metrics)
|
|
575
601
|
if (this.#metrics.length > MAX_METRICS_QUEUE_LENGTH) {
|
|
576
602
|
this.#metrics.shift()
|
|
@@ -1176,7 +1202,7 @@ export class Runtime extends EventEmitter {
|
|
|
1176
1202
|
|
|
1177
1203
|
#updateStatus (status, args) {
|
|
1178
1204
|
this.#status = status
|
|
1179
|
-
this.
|
|
1205
|
+
this.emitAndNotify(status, args)
|
|
1180
1206
|
}
|
|
1181
1207
|
|
|
1182
1208
|
#showUrl () {
|
|
@@ -1236,7 +1262,7 @@ export class Runtime extends EventEmitter {
|
|
|
1236
1262
|
|
|
1237
1263
|
await executeInParallel(this.#setupWorker.bind(this), setupInvocations, this.#concurrency)
|
|
1238
1264
|
|
|
1239
|
-
this.
|
|
1265
|
+
this.emitAndNotify('application:init', id)
|
|
1240
1266
|
}
|
|
1241
1267
|
|
|
1242
1268
|
async #setupWorker (config, applicationConfig, workersCount, applicationId, index, enabled = true) {
|
|
@@ -1345,7 +1371,7 @@ export class Runtime extends EventEmitter {
|
|
|
1345
1371
|
|
|
1346
1372
|
const started = worker[kWorkerStatus] === 'started'
|
|
1347
1373
|
worker[kWorkerStatus] = 'exited'
|
|
1348
|
-
this.
|
|
1374
|
+
this.emitAndNotify('application:worker:exited', eventPayload)
|
|
1349
1375
|
|
|
1350
1376
|
this.#cleanupWorker(worker)
|
|
1351
1377
|
|
|
@@ -1356,7 +1382,7 @@ export class Runtime extends EventEmitter {
|
|
|
1356
1382
|
// Wait for the next tick so that crashed from the thread are logged first
|
|
1357
1383
|
setImmediate(() => {
|
|
1358
1384
|
if (started && (!config.watch || code !== 0)) {
|
|
1359
|
-
this.
|
|
1385
|
+
this.emitAndNotify('application:worker:error', { ...eventPayload, code })
|
|
1360
1386
|
this.#broadcastWorkers()
|
|
1361
1387
|
|
|
1362
1388
|
this.logger.warn(`The ${errorLabel} unexpectedly exited with code ${code}.`)
|
|
@@ -1377,7 +1403,7 @@ export class Runtime extends EventEmitter {
|
|
|
1377
1403
|
}
|
|
1378
1404
|
)
|
|
1379
1405
|
} else {
|
|
1380
|
-
this.
|
|
1406
|
+
this.emitAndNotify('application:worker:unvailable', eventPayload)
|
|
1381
1407
|
this.logger.warn(`The ${errorLabel} is no longer available.`)
|
|
1382
1408
|
}
|
|
1383
1409
|
}
|
|
@@ -1407,8 +1433,12 @@ export class Runtime extends EventEmitter {
|
|
|
1407
1433
|
worker[kITC].listen()
|
|
1408
1434
|
|
|
1409
1435
|
// Forward events from the worker
|
|
1436
|
+
// Do not use emitAndNotify here since we don't want to forward unknown events
|
|
1410
1437
|
worker[kITC].on('event', ({ event, payload }) => {
|
|
1411
|
-
|
|
1438
|
+
event = `application:worker:event:${event}`
|
|
1439
|
+
|
|
1440
|
+
this.emit(event, ...payload)
|
|
1441
|
+
this.logger.trace({ event, payload }, 'Runtime event')
|
|
1412
1442
|
})
|
|
1413
1443
|
|
|
1414
1444
|
// Only activate watch for the first instance
|
|
@@ -1418,7 +1448,7 @@ export class Runtime extends EventEmitter {
|
|
|
1418
1448
|
// so that applications can eventually manually trigger a restart. This mechanism is current
|
|
1419
1449
|
// used by the gateway.
|
|
1420
1450
|
worker[kITC].on('changed', async () => {
|
|
1421
|
-
this.
|
|
1451
|
+
this.emitAndNotify('application:worker:changed', eventPayload)
|
|
1422
1452
|
|
|
1423
1453
|
try {
|
|
1424
1454
|
const wasStarted = worker[kWorkerStatus].startsWith('start')
|
|
@@ -1429,7 +1459,7 @@ export class Runtime extends EventEmitter {
|
|
|
1429
1459
|
}
|
|
1430
1460
|
|
|
1431
1461
|
this.logger.info(`The application "${applicationId}" has been successfully reloaded ...`)
|
|
1432
|
-
this.
|
|
1462
|
+
this.emitAndNotify('application:worker:reloaded', eventPayload)
|
|
1433
1463
|
|
|
1434
1464
|
if (applicationConfig.entrypoint) {
|
|
1435
1465
|
this.#showUrl()
|
|
@@ -1457,7 +1487,7 @@ export class Runtime extends EventEmitter {
|
|
|
1457
1487
|
|
|
1458
1488
|
worker[kConfig] = { ...applicationConfig, health, workers: workersCount }
|
|
1459
1489
|
worker[kWorkerStatus] = 'init'
|
|
1460
|
-
this.
|
|
1490
|
+
this.emitAndNotify('application:worker:init', eventPayload)
|
|
1461
1491
|
|
|
1462
1492
|
return worker
|
|
1463
1493
|
}
|
|
@@ -1501,7 +1531,7 @@ export class Runtime extends EventEmitter {
|
|
|
1501
1531
|
health = { elu: -1, heapUsed: -1, heapTotal: -1 }
|
|
1502
1532
|
}
|
|
1503
1533
|
|
|
1504
|
-
this.
|
|
1534
|
+
this.emitAndNotify('application:worker:health', {
|
|
1505
1535
|
id: worker[kId],
|
|
1506
1536
|
application: id,
|
|
1507
1537
|
worker: index,
|
|
@@ -1530,7 +1560,7 @@ export class Runtime extends EventEmitter {
|
|
|
1530
1560
|
|
|
1531
1561
|
if (unhealthyChecks === maxUnhealthyChecks) {
|
|
1532
1562
|
try {
|
|
1533
|
-
this.
|
|
1563
|
+
this.emitAndNotify('application:worker:unhealthy', { application: id, worker: index })
|
|
1534
1564
|
|
|
1535
1565
|
this.logger.error(
|
|
1536
1566
|
{ elu: health.elu, maxELU, memoryUsage: health.heapUsed, maxMemoryUsage: maxHeapUsed },
|
|
@@ -1582,7 +1612,7 @@ export class Runtime extends EventEmitter {
|
|
|
1582
1612
|
}
|
|
1583
1613
|
|
|
1584
1614
|
worker[kWorkerStatus] = 'starting'
|
|
1585
|
-
this.
|
|
1615
|
+
this.emitAndNotify('application:worker:starting', eventPayload)
|
|
1586
1616
|
|
|
1587
1617
|
try {
|
|
1588
1618
|
let workerUrl
|
|
@@ -1590,7 +1620,7 @@ export class Runtime extends EventEmitter {
|
|
|
1590
1620
|
workerUrl = await executeWithTimeout(sendViaITC(worker, 'start'), config.startTimeout)
|
|
1591
1621
|
|
|
1592
1622
|
if (workerUrl === kTimeout) {
|
|
1593
|
-
this.
|
|
1623
|
+
this.emitAndNotify('application:worker:startTimeout', eventPayload)
|
|
1594
1624
|
this.logger.info(`The ${label} failed to start in ${config.startTimeout}ms. Forcefully killing the thread.`)
|
|
1595
1625
|
worker.terminate()
|
|
1596
1626
|
throw new ApplicationStartTimeoutError(id, config.startTimeout)
|
|
@@ -1606,7 +1636,7 @@ export class Runtime extends EventEmitter {
|
|
|
1606
1636
|
}
|
|
1607
1637
|
|
|
1608
1638
|
worker[kWorkerStatus] = 'started'
|
|
1609
|
-
this.
|
|
1639
|
+
this.emitAndNotify('application:worker:started', eventPayload)
|
|
1610
1640
|
this.#broadcastWorkers()
|
|
1611
1641
|
|
|
1612
1642
|
if (!silent) {
|
|
@@ -1645,7 +1675,7 @@ export class Runtime extends EventEmitter {
|
|
|
1645
1675
|
}
|
|
1646
1676
|
}
|
|
1647
1677
|
|
|
1648
|
-
this.
|
|
1678
|
+
this.emitAndNotify('application:worker:start:error', { ...eventPayload, error })
|
|
1649
1679
|
|
|
1650
1680
|
if (error.code !== 'PLT_RUNTIME_APPLICATION_START_TIMEOUT') {
|
|
1651
1681
|
this.logger.error({ err: ensureLoggableError(error) }, `Failed to start ${label}: ${error.message}`)
|
|
@@ -1659,7 +1689,7 @@ export class Runtime extends EventEmitter {
|
|
|
1659
1689
|
|
|
1660
1690
|
if (bootstrapAttempt++ >= MAX_BOOTSTRAP_ATTEMPTS || restartOnError === 0) {
|
|
1661
1691
|
this.logger.error(`Failed to start ${label} after ${MAX_BOOTSTRAP_ATTEMPTS} attempts.`)
|
|
1662
|
-
this.
|
|
1692
|
+
this.emitAndNotify('application:worker:start:failed', { ...eventPayload, error })
|
|
1663
1693
|
throw error
|
|
1664
1694
|
}
|
|
1665
1695
|
|
|
@@ -1695,7 +1725,7 @@ export class Runtime extends EventEmitter {
|
|
|
1695
1725
|
|
|
1696
1726
|
worker[kWorkerStatus] = 'stopping'
|
|
1697
1727
|
worker[kITC].removeAllListeners('changed')
|
|
1698
|
-
this.
|
|
1728
|
+
this.emitAndNotify('application:worker:stopping', eventPayload)
|
|
1699
1729
|
|
|
1700
1730
|
const label = this.#workerExtendedLabel(id, index, workersCount)
|
|
1701
1731
|
|
|
@@ -1710,7 +1740,7 @@ export class Runtime extends EventEmitter {
|
|
|
1710
1740
|
try {
|
|
1711
1741
|
await executeWithTimeout(sendViaITC(worker, 'stop', { force: !!this.error, dependents }), exitTimeout)
|
|
1712
1742
|
} catch (error) {
|
|
1713
|
-
this.
|
|
1743
|
+
this.emitAndNotify('application:worker:stop:error', eventPayload)
|
|
1714
1744
|
this.logger.info({ error: ensureLoggableError(error) }, `Failed to stop ${label}. Killing a worker thread.`)
|
|
1715
1745
|
} finally {
|
|
1716
1746
|
worker[kITC].notify('application:worker:stop:processed')
|
|
@@ -1729,14 +1759,14 @@ export class Runtime extends EventEmitter {
|
|
|
1729
1759
|
|
|
1730
1760
|
// If the worker didn't exit in time, kill it
|
|
1731
1761
|
if (res === kTimeout) {
|
|
1732
|
-
this.
|
|
1762
|
+
this.emitAndNotify('application:worker:exit:timeout', eventPayload)
|
|
1733
1763
|
await worker.terminate()
|
|
1734
1764
|
}
|
|
1735
1765
|
|
|
1736
1766
|
await this.#avoidOutOfOrderThreadLogs()
|
|
1737
1767
|
|
|
1738
1768
|
worker[kWorkerStatus] = 'stopped'
|
|
1739
|
-
this.
|
|
1769
|
+
this.emitAndNotify('application:worker:stopped', eventPayload)
|
|
1740
1770
|
this.#broadcastWorkers()
|
|
1741
1771
|
}
|
|
1742
1772
|
|
|
@@ -1814,11 +1844,16 @@ export class Runtime extends EventEmitter {
|
|
|
1814
1844
|
await restartPromise
|
|
1815
1845
|
}
|
|
1816
1846
|
|
|
1817
|
-
async #replaceWorker (config, applicationConfig, workersCount, applicationId, index, worker) {
|
|
1847
|
+
async #replaceWorker (config, applicationConfig, workersCount, applicationId, index, worker, silent) {
|
|
1818
1848
|
const workerId = `${applicationId}:${index}`
|
|
1849
|
+
const label = this.#workerExtendedLabel(applicationId, index, workersCount)
|
|
1819
1850
|
let newWorker
|
|
1820
1851
|
|
|
1821
1852
|
try {
|
|
1853
|
+
if (!silent) {
|
|
1854
|
+
this.logger.debug(`Preparing to start a replacement for ${label} ...`)
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1822
1857
|
// Create a new worker
|
|
1823
1858
|
newWorker = await this.#setupWorker(config, applicationConfig, workersCount, applicationId, index, false)
|
|
1824
1859
|
|
|
@@ -1845,6 +1880,9 @@ export class Runtime extends EventEmitter {
|
|
|
1845
1880
|
throw e
|
|
1846
1881
|
}
|
|
1847
1882
|
|
|
1883
|
+
if (!silent) {
|
|
1884
|
+
this.logger.debug(`Preparing to stop the old version of ${label} ...`)
|
|
1885
|
+
}
|
|
1848
1886
|
await this.#stopWorker(workersCount, applicationId, index, false, worker, [])
|
|
1849
1887
|
}
|
|
1850
1888
|
|
|
@@ -1952,7 +1990,7 @@ export class Runtime extends EventEmitter {
|
|
|
1952
1990
|
}
|
|
1953
1991
|
|
|
1954
1992
|
context.transferList = [port2]
|
|
1955
|
-
this.
|
|
1993
|
+
this.emitAndNotify('application:worker:messagingChannel', { application, worker })
|
|
1956
1994
|
return port2
|
|
1957
1995
|
}
|
|
1958
1996
|
|
package/lib/worker/itc.js
CHANGED
|
@@ -70,12 +70,14 @@ export async function waitEventFromITC (worker, event) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
73
|
-
const
|
|
73
|
+
const logger = globalThis.platformatic.logger
|
|
74
|
+
const messaging = new MessagingITC(controller.appConfig.id, workerData.config, logger)
|
|
74
75
|
|
|
75
76
|
Object.assign(globalThis.platformatic ?? {}, {
|
|
76
77
|
messaging: {
|
|
77
78
|
handle: messaging.handle.bind(messaging),
|
|
78
|
-
send: messaging.send.bind(messaging)
|
|
79
|
+
send: messaging.send.bind(messaging),
|
|
80
|
+
notify: messaging.notify.bind(messaging)
|
|
79
81
|
}
|
|
80
82
|
})
|
|
81
83
|
|
package/lib/worker/main.js
CHANGED
|
@@ -23,6 +23,13 @@ import { setupITC } from './itc.js'
|
|
|
23
23
|
import { SharedContext } from './shared-context.js'
|
|
24
24
|
import { kId, kITC, kStderrMarker } from './symbols.js'
|
|
25
25
|
|
|
26
|
+
class ForwardingEventEmitter extends EventEmitter {
|
|
27
|
+
emitAndNotify (event, ...args) {
|
|
28
|
+
globalThis.platformatic.itc.notify('event', { event, payload: args })
|
|
29
|
+
return this.emit(event, ...args)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
function handleUnhandled (app, type, err) {
|
|
27
34
|
const label =
|
|
28
35
|
workerData.worker.count > 1
|
|
@@ -103,7 +110,7 @@ async function main () {
|
|
|
103
110
|
globalThis[kId] = threadId
|
|
104
111
|
globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, {
|
|
105
112
|
logger: createLogger(),
|
|
106
|
-
events: new
|
|
113
|
+
events: new ForwardingEventEmitter()
|
|
107
114
|
})
|
|
108
115
|
|
|
109
116
|
const config = workerData.config
|
|
@@ -217,6 +224,7 @@ async function main () {
|
|
|
217
224
|
// Setup interaction with parent port
|
|
218
225
|
const itc = setupITC(controller, application, threadDispatcher, sharedContext)
|
|
219
226
|
globalThis[kITC] = itc
|
|
227
|
+
globalThis.platformatic.itc = itc
|
|
220
228
|
|
|
221
229
|
itc.notify('init')
|
|
222
230
|
}
|
package/lib/worker/messaging.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { executeWithTimeout, kTimeout } from '@platformatic/foundation'
|
|
2
|
-
import { ITC, generateResponse, sanitize } from '@platformatic/itc'
|
|
1
|
+
import { executeWithTimeout, ensureLoggableError, kTimeout } from '@platformatic/foundation'
|
|
2
|
+
import { ITC, parseRequest, generateRequest, generateResponse, sanitize, errors } from '@platformatic/itc'
|
|
3
3
|
import { MessagingError } from '../errors.js'
|
|
4
4
|
import { RoundRobinMap } from './round-robin-map.js'
|
|
5
5
|
import { kITC, kWorkersBroadcast } from './symbols.js'
|
|
@@ -11,10 +11,12 @@ export class MessagingITC extends ITC {
|
|
|
11
11
|
#listener
|
|
12
12
|
#closeResolvers
|
|
13
13
|
#broadcastChannel
|
|
14
|
+
#notificationsChannels
|
|
14
15
|
#workers
|
|
15
16
|
#sources
|
|
17
|
+
#logger
|
|
16
18
|
|
|
17
|
-
constructor (id, runtimeConfig) {
|
|
19
|
+
constructor (id, runtimeConfig, logger) {
|
|
18
20
|
super({
|
|
19
21
|
throwOnMissingHandler: true,
|
|
20
22
|
name: `${id}-messaging`
|
|
@@ -28,6 +30,14 @@ export class MessagingITC extends ITC {
|
|
|
28
30
|
this.#broadcastChannel = new BroadcastChannel(kWorkersBroadcast)
|
|
29
31
|
this.#broadcastChannel.onmessage = this.#updateWorkers.bind(this)
|
|
30
32
|
|
|
33
|
+
this.#notificationsChannels = new Map()
|
|
34
|
+
|
|
35
|
+
const notificationsChannel = new BroadcastChannel(`plt.messaging.notifications-${id}`)
|
|
36
|
+
notificationsChannel.onmessage = this.#handleNotification.bind(this)
|
|
37
|
+
this.#notificationsChannels.set(id, notificationsChannel)
|
|
38
|
+
|
|
39
|
+
this.#logger = logger
|
|
40
|
+
|
|
31
41
|
this.listen()
|
|
32
42
|
}
|
|
33
43
|
|
|
@@ -87,6 +97,18 @@ export class MessagingITC extends ITC {
|
|
|
87
97
|
return response
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
notify (application, name, message) {
|
|
101
|
+
const request = generateRequest(name, message)
|
|
102
|
+
|
|
103
|
+
let channel = this.#notificationsChannels.get(application)
|
|
104
|
+
if (!channel) {
|
|
105
|
+
channel = new BroadcastChannel(`plt.messaging.notifications-${application}`)
|
|
106
|
+
this.#notificationsChannels.set(application, channel)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
channel.postMessage(sanitize(request))
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
async addSource (channel) {
|
|
91
113
|
this.#sources.add(channel)
|
|
92
114
|
this.#setupChannel(channel)
|
|
@@ -119,6 +141,10 @@ export class MessagingITC extends ITC {
|
|
|
119
141
|
this.#closeResolvers.resolve()
|
|
120
142
|
this.#broadcastChannel.close()
|
|
121
143
|
|
|
144
|
+
for (const channel of this.#notificationsChannels.values()) {
|
|
145
|
+
channel.close()
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
for (const source of this.#sources) {
|
|
123
149
|
source.close()
|
|
124
150
|
}
|
|
@@ -166,6 +192,27 @@ export class MessagingITC extends ITC {
|
|
|
166
192
|
this.#workers.configure(instances)
|
|
167
193
|
}
|
|
168
194
|
|
|
195
|
+
async #handleNotification (messageEvent) {
|
|
196
|
+
let request
|
|
197
|
+
try {
|
|
198
|
+
request = parseRequest(messageEvent.data)
|
|
199
|
+
} catch (err) {
|
|
200
|
+
this.#logger.error({ err: ensureLoggableError(err) }, 'Failed to parse the notification message.')
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const handler = this.getHandler(request.name)
|
|
206
|
+
if (!handler) {
|
|
207
|
+
throw new errors.HandlerNotFoundError(request.name)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await handler(request.data)
|
|
211
|
+
} catch (error) {
|
|
212
|
+
this.#logger.error({ error }, `"Handler for the "${request.name}" message failed.`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
169
216
|
#handlePendingResponse (channel) {
|
|
170
217
|
for (const { application, request } of channel[kPendingResponses].values()) {
|
|
171
218
|
this._emitResponse(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
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/
|
|
38
|
-
"@platformatic/
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/node": "3.
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/sql-
|
|
43
|
-
"@platformatic/
|
|
44
|
-
"@platformatic/
|
|
37
|
+
"@platformatic/composer": "3.7.0",
|
|
38
|
+
"@platformatic/db": "3.7.0",
|
|
39
|
+
"@platformatic/gateway": "3.7.0",
|
|
40
|
+
"@platformatic/node": "3.7.0",
|
|
41
|
+
"@platformatic/sql-graphql": "3.7.0",
|
|
42
|
+
"@platformatic/sql-mapper": "3.7.0",
|
|
43
|
+
"@platformatic/service": "3.7.0",
|
|
44
|
+
"@platformatic/wattpm-pprof-capture": "3.7.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@platformatic/undici-cache-memory": "^0.8.1",
|
|
53
53
|
"@watchable/unpromise": "^1.0.2",
|
|
54
54
|
"change-case-all": "^2.1.0",
|
|
55
|
-
"close-with-grace": "^2.
|
|
55
|
+
"close-with-grace": "^2.3.0",
|
|
56
56
|
"colorette": "^2.0.20",
|
|
57
57
|
"cron": "^4.1.0",
|
|
58
58
|
"debounce": "^2.0.0",
|
|
@@ -71,12 +71,12 @@
|
|
|
71
71
|
"undici": "^7.0.0",
|
|
72
72
|
"undici-thread-interceptor": "^0.14.0",
|
|
73
73
|
"ws": "^8.16.0",
|
|
74
|
-
"@platformatic/
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/
|
|
74
|
+
"@platformatic/foundation": "3.7.0",
|
|
75
|
+
"@platformatic/basic": "3.7.0",
|
|
76
|
+
"@platformatic/itc": "3.7.0",
|
|
77
|
+
"@platformatic/metrics": "3.7.0",
|
|
78
|
+
"@platformatic/generators": "3.7.0",
|
|
79
|
+
"@platformatic/telemetry": "3.7.0"
|
|
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.7.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -926,6 +926,18 @@
|
|
|
926
926
|
],
|
|
927
927
|
"default": 1
|
|
928
928
|
},
|
|
929
|
+
"workersRestartDelay": {
|
|
930
|
+
"anyOf": [
|
|
931
|
+
{
|
|
932
|
+
"type": "number",
|
|
933
|
+
"minimum": 0
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"type": "string"
|
|
937
|
+
}
|
|
938
|
+
],
|
|
939
|
+
"default": 0
|
|
940
|
+
},
|
|
929
941
|
"logger": {
|
|
930
942
|
"type": "object",
|
|
931
943
|
"properties": {
|