@platformatic/runtime 3.71.1-alpha.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.d.ts +602 -673
- package/index.d.ts +22 -27
- package/index.js +290 -30
- package/lib/config.js +270 -367
- package/lib/errors.js +27 -13
- package/lib/generator.js +249 -127
- package/lib/logger.js +3 -3
- package/lib/management-api.js +22 -65
- package/lib/management-handlers.js +3 -5
- package/lib/memory.js +29 -0
- package/lib/policies.js +27 -0
- package/lib/predictive-scaling.js +910 -0
- package/lib/predictive-worker-scaler.js +378 -0
- package/lib/runtime.js +1081 -572
- package/lib/scheduler.js +6 -41
- package/lib/schema.js +77 -15
- package/lib/upgrade.js +16 -1
- package/lib/versions/v4.0.0.js +31 -0
- package/lib/worker/controller.js +89 -109
- package/lib/worker/interceptors.js +34 -34
- package/lib/worker/itc.js +248 -66
- package/lib/worker/main.js +50 -98
- package/lib/worker/messaging.js +42 -1
- package/lib/worker/round-robin-map.js +10 -3
- package/lib/worker/symbols.js +6 -2
- package/package.json +29 -20
- package/schema.json +252 -433
- package/lib/scaling-algorithm.js +0 -256
- package/lib/worker-scaler.js +0 -249
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
getOnHttpCacheMiss,
|
|
7
7
|
getOnHttpCacheRequest
|
|
8
8
|
} from '@platformatic/globals'
|
|
9
|
-
import { createTelemetryThreadInterceptorHooks } from '@platformatic/
|
|
9
|
+
import { createTelemetryThreadInterceptorHooks } from '@platformatic/tracing'
|
|
10
10
|
import { createRequire } from 'node:module'
|
|
11
11
|
import { join } from 'node:path'
|
|
12
12
|
import { pathToFileURL } from 'node:url'
|
|
13
|
-
import {
|
|
14
|
-
import { Agent, Client, Pool } from 'undici'
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { workerData } from 'node:worker_threads'
|
|
14
|
+
import { Agent, Client, Pool, setGlobalDispatcher } from 'undici'
|
|
15
|
+
import { createInterceptor } from 'undici-thread-interceptor'
|
|
16
|
+
import { createTargetPermissionHook } from '../policies.js'
|
|
17
17
|
import { RemoteCacheStore, httpCacheInterceptor } from './http-cache.js'
|
|
18
18
|
|
|
19
19
|
const kPlatformaticGlobalDispatcher = Symbol.for('platformatic.undici.globalDispatcher')
|
|
@@ -43,8 +43,11 @@ export function refreshGlobalDispatcher () {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export async function setDispatcher (runtimeConfig) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
46
|
+
const tracingHooks = runtimeConfig.tracing ? createTelemetryThreadInterceptorHooks() : {}
|
|
47
|
+
const threadDispatcher = createThreadInterceptor(runtimeConfig, tracingHooks)
|
|
48
|
+
const threadInterceptor = threadDispatcher
|
|
49
|
+
|
|
50
|
+
await threadDispatcher.ready
|
|
48
51
|
|
|
49
52
|
let cacheInterceptor = null
|
|
50
53
|
if (runtimeConfig.httpCache) {
|
|
@@ -65,30 +68,29 @@ export async function setDispatcher (runtimeConfig) {
|
|
|
65
68
|
[threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean)
|
|
66
69
|
)
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
mirrorGlobalDispatcherForBuiltinFetch(dispatcher, createLegacyDispatcher(dispatcher))
|
|
71
|
+
installGlobalDispatcher(dispatcher)
|
|
70
72
|
return dispatcher
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
composeRuntimeDispatcher = installDispatcher
|
|
74
76
|
installDispatcher(new Agent(dispatcherOpts))
|
|
75
77
|
|
|
76
|
-
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
78
|
+
return {
|
|
79
|
+
threadDispatcher: {
|
|
80
|
+
interceptor: threadDispatcher,
|
|
81
|
+
server: null,
|
|
82
|
+
serverHooks: tracingHooks.serverHooks
|
|
88
83
|
}
|
|
89
84
|
}
|
|
85
|
+
}
|
|
90
86
|
|
|
91
|
-
|
|
87
|
+
export function installGlobalDispatcher (dispatcher) {
|
|
88
|
+
markAsPlatformaticDispatcher(dispatcher)
|
|
89
|
+
setGlobalDispatcher(dispatcher)
|
|
90
|
+
mirrorGlobalDispatcherForBuiltinFetch(
|
|
91
|
+
dispatcher,
|
|
92
|
+
globalThis[Symbol.for('undici.globalDispatcher.1')]
|
|
93
|
+
)
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
export async function updateUndiciInterceptors (undiciConfig) {
|
|
@@ -215,19 +217,17 @@ async function getDispatcherOpts (undiciConfig) {
|
|
|
215
217
|
return dispatcherOpts
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
function createThreadInterceptor (runtimeConfig) {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const telemetryHooks = telemetry ? createTelemetryThreadInterceptorHooks() : {}
|
|
222
|
-
|
|
223
|
-
const threadDispatcher = wire({
|
|
224
|
-
// Specifying the domain is critical to avoid flooding the DNS
|
|
225
|
-
// with requests for a domain that's never going to exist.
|
|
220
|
+
function createThreadInterceptor (runtimeConfig, tracingHooks) {
|
|
221
|
+
const threadDispatcher = createInterceptor({
|
|
222
|
+
meshId: workerData.meshId,
|
|
226
223
|
domain: '.plt.local',
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
224
|
+
connectTimeout: runtimeConfig.applicationTimeout,
|
|
225
|
+
bootstrapTimeout: runtimeConfig.applicationTimeout,
|
|
226
|
+
allowTarget: createTargetPermissionHook({
|
|
227
|
+
...runtimeConfig,
|
|
228
|
+
applicationId: workerData.applicationConfig.id
|
|
229
|
+
}),
|
|
230
|
+
...tracingHooks
|
|
231
231
|
})
|
|
232
232
|
return threadDispatcher
|
|
233
233
|
}
|
package/lib/worker/itc.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { servingState } from '@platformatic/basic'
|
|
2
|
+
import {
|
|
3
|
+
ensureError,
|
|
4
|
+
ensureLoggableError,
|
|
5
|
+
executeInParallel,
|
|
6
|
+
executeWithTimeout,
|
|
7
|
+
kTimeout
|
|
8
|
+
} from '@platformatic/foundation'
|
|
9
|
+
import { consumeCloseCallbacks, getEvents, getLogger, getMessaging, updateGlobals } from '@platformatic/globals'
|
|
3
10
|
import { ITC, initializeITCTelemetry } from '@platformatic/itc'
|
|
4
11
|
import { Unpromise } from '@watchable/unpromise'
|
|
5
12
|
import { once } from 'node:events'
|
|
6
13
|
import { createRequire } from 'node:module'
|
|
7
14
|
import { Duplex } from 'node:stream'
|
|
8
15
|
import { parentPort, workerData } from 'node:worker_threads'
|
|
16
|
+
import { createServer } from 'undici-thread-interceptor'
|
|
9
17
|
import {
|
|
10
18
|
ApplicationExitedError,
|
|
19
|
+
ApplicationShutdownError,
|
|
11
20
|
FailedToPerformCustomHealthCheckError,
|
|
12
21
|
FailedToPerformCustomReadinessCheckError,
|
|
13
22
|
FailedToRetrieveGraphQLSchemaError,
|
|
@@ -15,12 +24,13 @@ import {
|
|
|
15
24
|
FailedToRetrieveMetaError,
|
|
16
25
|
FailedToRetrieveMetricsError,
|
|
17
26
|
FailedToRetrieveOpenAPISchemaError,
|
|
27
|
+
RuntimeAbortedError,
|
|
18
28
|
WorkerExitedError,
|
|
19
29
|
exitCodes
|
|
20
30
|
} from '../errors.js'
|
|
21
31
|
import { updateUndiciInterceptors } from './interceptors.js'
|
|
22
32
|
import { MessagingITC } from './messaging.js'
|
|
23
|
-
import { kApplicationId, kITC, kId, kWorkerId } from './symbols.js'
|
|
33
|
+
import { kApplicationId, kITC, kId, kWorkerExitCode, kWorkerId } from './symbols.js'
|
|
24
34
|
|
|
25
35
|
function startSubprocessRepl (port, childManager, clientWs, controller) {
|
|
26
36
|
// Start the REPL in the child process
|
|
@@ -78,14 +88,18 @@ async function safeHandleInITC (worker, fn) {
|
|
|
78
88
|
try {
|
|
79
89
|
// Make sure to catch when the worker exits, otherwise we're stuck forever
|
|
80
90
|
const ac = new AbortController()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
// A worker which already exited will not emit the exit event again
|
|
92
|
+
let exitCode = worker[kWorkerExitCode]
|
|
93
|
+
let response
|
|
94
|
+
|
|
95
|
+
if (typeof exitCode !== 'number') {
|
|
96
|
+
response = await Unpromise.race([
|
|
97
|
+
fn(),
|
|
98
|
+
once(worker, 'exit', { signal: ac.signal }).then(([code]) => {
|
|
99
|
+
exitCode = code
|
|
100
|
+
})
|
|
101
|
+
])
|
|
102
|
+
}
|
|
89
103
|
|
|
90
104
|
if (typeof exitCode === 'number') {
|
|
91
105
|
if (typeof worker[kWorkerId] !== 'undefined' && exitCode !== exitCodes.PROCESS_UNHANDLED_ERROR) {
|
|
@@ -103,22 +117,142 @@ async function safeHandleInITC (worker, fn) {
|
|
|
103
117
|
throw error
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
const handlerError = ensureError(error.handlerError)
|
|
121
|
+
if (error.handlerErrorCode && !handlerError.code) {
|
|
122
|
+
handlerError.code = error.handlerErrorCode
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
throw
|
|
125
|
+
throw handlerError
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
|
|
114
|
-
async function
|
|
129
|
+
async function closeResource (close, errors) {
|
|
115
130
|
try {
|
|
116
|
-
await
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
await close()
|
|
132
|
+
} catch (error) {
|
|
133
|
+
errors.push(error)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function stopApplication (context, force, dependents, shutdownTimeout) {
|
|
138
|
+
const { controller, dispatcher, itc, messaging } = context
|
|
139
|
+
const errors = []
|
|
140
|
+
let controllerError
|
|
141
|
+
|
|
142
|
+
if (force || controller.getStatus().startsWith('start')) {
|
|
143
|
+
try {
|
|
144
|
+
getEvents().emit('stop')
|
|
145
|
+
} catch (error) {
|
|
146
|
+
errors.push(error)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await controller.stop(force, dependents, shutdownTimeout)
|
|
151
|
+
} catch (error) {
|
|
152
|
+
controllerError = error
|
|
153
|
+
errors.push(error)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
errors.push(...(await runShutdownCallbacks()))
|
|
158
|
+
// Keep communication usable while signal handlers drain. Only idle channels
|
|
159
|
+
// are unreferenced; pending mesh and messaging operations keep the worker alive.
|
|
160
|
+
const drained = once(process, 'beforeExit')
|
|
161
|
+
dispatcher.interceptor.unref()
|
|
162
|
+
dispatcher.server?.unref()
|
|
163
|
+
messaging.unref()
|
|
164
|
+
itc.port.unref()
|
|
165
|
+
await drained
|
|
166
|
+
itc.port.ref()
|
|
167
|
+
await closeResource(() => closeApplicationChannels(dispatcher, messaging), errors)
|
|
168
|
+
|
|
169
|
+
if (errors.length > 0) {
|
|
170
|
+
if (errors.length === 1 && errors[0] === controllerError && controllerError?.code) {
|
|
171
|
+
throw controllerError
|
|
172
|
+
}
|
|
173
|
+
throw new ApplicationShutdownError(errors)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function stopController (context, force, dependents, shutdownTimeout, stopProcessed) {
|
|
178
|
+
try {
|
|
179
|
+
if (context.controller.getStatus() === 'starting') {
|
|
180
|
+
// The start handler reports its own error and shares cleanup with us.
|
|
181
|
+
await context.controllerStartPromise.catch(() => {})
|
|
182
|
+
}
|
|
183
|
+
if (!context.applicationStopPromise) {
|
|
184
|
+
const { promise, resolve, reject } = Promise.withResolvers()
|
|
185
|
+
context.applicationStopPromise = promise
|
|
186
|
+
stopApplication(context, force, dependents, shutdownTimeout).then(resolve, reject)
|
|
187
|
+
}
|
|
188
|
+
await context.applicationStopPromise
|
|
119
189
|
} finally {
|
|
190
|
+
// The acknowledgement only gates transport closure. Application work has already
|
|
191
|
+
// drained before the response, including work scheduled by SIGINT after failed startup.
|
|
192
|
+
stopProcessed.then(() => {
|
|
193
|
+
context.internalClosePromise ??= closeITC(context.itc)
|
|
194
|
+
context.internalClosePromise.catch(err => {
|
|
195
|
+
context.logger.error({ err: ensureLoggableError(err) }, 'Failed to close the worker ITC after stop.')
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function runShutdownCallbacks () {
|
|
202
|
+
const errors = []
|
|
203
|
+
const callbacks = consumeCloseCallbacks().reverse()
|
|
204
|
+
|
|
205
|
+
for (const callback of callbacks) {
|
|
206
|
+
try {
|
|
207
|
+
await callback()
|
|
208
|
+
} catch (error) {
|
|
209
|
+
errors.push(error)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const signalListeners = process.listeners('SIGINT')
|
|
214
|
+
for (const listener of signalListeners) {
|
|
215
|
+
process.removeListener('SIGINT', listener)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const listener of signalListeners) {
|
|
219
|
+
try {
|
|
220
|
+
// Like a signal emission, ignore return values without consuming rejected promises.
|
|
221
|
+
listener.call(process, 'SIGINT')
|
|
222
|
+
} catch (error) {
|
|
223
|
+
errors.push(error)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return errors
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function closeApplicationChannels (dispatcher, messaging) {
|
|
231
|
+
const errors = []
|
|
232
|
+
|
|
233
|
+
await closeResource(() => dispatcher.interceptor.close(), errors)
|
|
234
|
+
await closeResource(() => dispatcher.server?.close(), errors)
|
|
235
|
+
await closeResource(() => messaging.close(), errors)
|
|
236
|
+
|
|
237
|
+
if (errors.length > 0) {
|
|
238
|
+
throw new ApplicationShutdownError(errors)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function closeITC (itc) {
|
|
243
|
+
const errors = []
|
|
244
|
+
// ITC closes the transport after all active handlers have sent their responses.
|
|
245
|
+
await closeResource(() => itc.close(), errors)
|
|
246
|
+
|
|
247
|
+
try {
|
|
120
248
|
const events = getEvents()
|
|
121
249
|
events.emit('exit')
|
|
250
|
+
} catch (error) {
|
|
251
|
+
errors.push(error)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (errors.length > 0) {
|
|
255
|
+
throw new ApplicationShutdownError(errors)
|
|
122
256
|
}
|
|
123
257
|
}
|
|
124
258
|
|
|
@@ -169,7 +303,18 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
169
303
|
|
|
170
304
|
// ITC handlers run concurrently, so later start/stop requests must wait for
|
|
171
305
|
// the actual in-flight start operation rather than a controller event.
|
|
172
|
-
|
|
306
|
+
const context = {
|
|
307
|
+
controller,
|
|
308
|
+
dispatcher,
|
|
309
|
+
itc: null,
|
|
310
|
+
messaging,
|
|
311
|
+
logger,
|
|
312
|
+
controllerStartPromise: null,
|
|
313
|
+
controllerStopPromise: null,
|
|
314
|
+
abortStart: false,
|
|
315
|
+
applicationStopPromise: null,
|
|
316
|
+
internalClosePromise: null
|
|
317
|
+
}
|
|
173
318
|
|
|
174
319
|
const itc = new ITC({
|
|
175
320
|
name: controller.applicationConfig.id + '-worker',
|
|
@@ -179,37 +324,58 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
179
324
|
const status = controller.getStatus()
|
|
180
325
|
|
|
181
326
|
if (status === 'starting') {
|
|
182
|
-
await controllerStartPromise
|
|
327
|
+
await context.controllerStartPromise
|
|
183
328
|
} else {
|
|
184
329
|
// This gives a chance to a capability to perform custom logic
|
|
185
330
|
const events = getEvents()
|
|
186
331
|
events.emit('start')
|
|
187
332
|
|
|
188
333
|
try {
|
|
189
|
-
controllerStartPromise = controller.start()
|
|
190
|
-
await controllerStartPromise
|
|
334
|
+
context.controllerStartPromise = controller.start()
|
|
335
|
+
await context.controllerStartPromise
|
|
336
|
+
if (context.abortStart) {
|
|
337
|
+
throw new RuntimeAbortedError()
|
|
338
|
+
}
|
|
191
339
|
} catch (e) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
throw
|
|
340
|
+
const startProcessed = once(itc, 'application:worker:start:processed')
|
|
341
|
+
try {
|
|
342
|
+
await stopController(context, true, [], undefined, startProcessed)
|
|
343
|
+
} catch (error) {
|
|
344
|
+
logger.error(
|
|
345
|
+
{ err: ensureLoggableError(error) },
|
|
346
|
+
'Errors occurred while cleaning up a failed application start.'
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Errors are structured cloned when sent to the runtime, which drops all their custom properties (like the
|
|
351
|
+
// port and the address of listen errors): send a plain object instead so that the runtime can inspect them.
|
|
352
|
+
// eslint-disable-next-line no-throw-literal
|
|
353
|
+
throw { name: e.name, ...ensureLoggableError(e) }
|
|
205
354
|
}
|
|
206
355
|
}
|
|
207
356
|
|
|
208
|
-
|
|
209
|
-
|
|
357
|
+
const dispatchTarget = await controller.capability.getDispatchTarget()
|
|
358
|
+
if (dispatchTarget == null) {
|
|
359
|
+
await new Promise(() => {})
|
|
210
360
|
}
|
|
211
361
|
|
|
212
|
-
dispatcher.
|
|
362
|
+
const serverTarget = dispatcher.serverHooks?.run
|
|
363
|
+
? wrapDispatchTarget(dispatchTarget, dispatcher.serverHooks.run)
|
|
364
|
+
: dispatchTarget
|
|
365
|
+
|
|
366
|
+
dispatcher.server = createServer({
|
|
367
|
+
meshId: workerData.meshId,
|
|
368
|
+
serverId: workerData.worker.id,
|
|
369
|
+
domain: `${application.id}.plt.local`,
|
|
370
|
+
server: serverTarget,
|
|
371
|
+
bootstrapTimeout: workerData.config.applicationTimeout,
|
|
372
|
+
metadata: {
|
|
373
|
+
applicationId: application.id,
|
|
374
|
+
workerId: workerData.worker.id
|
|
375
|
+
},
|
|
376
|
+
...dispatcher.serverHooks
|
|
377
|
+
})
|
|
378
|
+
await dispatcher.server.ready
|
|
213
379
|
|
|
214
380
|
const scheduledTasks =
|
|
215
381
|
typeof controller.capability.getScheduledTasks === 'function'
|
|
@@ -217,38 +383,28 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
217
383
|
: []
|
|
218
384
|
|
|
219
385
|
return {
|
|
220
|
-
url:
|
|
386
|
+
url: controller.getUrl(),
|
|
221
387
|
scheduledTasks
|
|
222
388
|
}
|
|
223
389
|
},
|
|
224
390
|
|
|
225
|
-
async stop ({ force, dependents }) {
|
|
226
|
-
|
|
227
|
-
|
|
391
|
+
async stop ({ force, dependents, shutdownTimeout }) {
|
|
392
|
+
if (context.controllerStopPromise) {
|
|
393
|
+
return context.controllerStopPromise
|
|
394
|
+
}
|
|
395
|
+
context.abortStart = !!force
|
|
228
396
|
|
|
229
|
-
|
|
230
|
-
await controllerStartPromise
|
|
231
|
-
}
|
|
397
|
+
const stopProcessed = once(itc, 'application:worker:stop:processed')
|
|
232
398
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
399
|
+
context.controllerStopPromise = stopController(
|
|
400
|
+
context,
|
|
401
|
+
force,
|
|
402
|
+
dependents,
|
|
403
|
+
shutdownTimeout,
|
|
404
|
+
stopProcessed
|
|
405
|
+
)
|
|
237
406
|
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
} finally {
|
|
241
|
-
// Always schedule cleanup, even when stop throws. Otherwise the worker
|
|
242
|
-
// keeps open handles and runtime shutdown hangs until the grace timeout.
|
|
243
|
-
once(itc, 'application:worker:stop:processed').then(() => {
|
|
244
|
-
closeITC(dispatcher, itc, messaging).catch(err => {
|
|
245
|
-
logger.error(
|
|
246
|
-
{ err: ensureLoggableError(err) },
|
|
247
|
-
'Failed to close the worker ITC after stop.'
|
|
248
|
-
)
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
|
-
}
|
|
407
|
+
return context.controllerStopPromise
|
|
252
408
|
},
|
|
253
409
|
|
|
254
410
|
async getDependencies () {
|
|
@@ -260,7 +416,8 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
260
416
|
},
|
|
261
417
|
|
|
262
418
|
async removeFromMesh () {
|
|
263
|
-
|
|
419
|
+
await dispatcher.server?.close()
|
|
420
|
+
dispatcher.server = null
|
|
264
421
|
},
|
|
265
422
|
|
|
266
423
|
inject (injectParams) {
|
|
@@ -292,6 +449,12 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
292
449
|
return controller.capability.getInfo()
|
|
293
450
|
},
|
|
294
451
|
|
|
452
|
+
// How this worker serves, which only the worker can answer: for a worker-classified
|
|
453
|
+
// capability it depends on what the application's factory returned in this worker.
|
|
454
|
+
getServingState () {
|
|
455
|
+
return controller.capability?.getServingState?.() ?? servingState.inactive
|
|
456
|
+
},
|
|
457
|
+
|
|
295
458
|
async getApplicationConfig () {
|
|
296
459
|
const current = await controller.capability.getConfig()
|
|
297
460
|
// Remove all undefined keys from the config
|
|
@@ -401,11 +564,11 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
401
564
|
const session = new Session()
|
|
402
565
|
session.connect()
|
|
403
566
|
|
|
404
|
-
session.on('HeapProfiler.addHeapSnapshotChunk',
|
|
567
|
+
session.on('HeapProfiler.addHeapSnapshotChunk', m => {
|
|
405
568
|
port.postMessage({ type: 'chunk', chunk: m.params.chunk })
|
|
406
569
|
})
|
|
407
570
|
|
|
408
|
-
session.post('HeapProfiler.takeHeapSnapshot', null,
|
|
571
|
+
session.post('HeapProfiler.takeHeapSnapshot', null, err => {
|
|
409
572
|
session.disconnect()
|
|
410
573
|
if (err) {
|
|
411
574
|
port.postMessage({ type: 'error', message: err.message })
|
|
@@ -496,6 +659,25 @@ export async function setupITC (controller, application, dispatcher, sharedConte
|
|
|
496
659
|
itc.notify('changed')
|
|
497
660
|
})
|
|
498
661
|
|
|
662
|
+
context.itc = itc
|
|
663
|
+
|
|
499
664
|
itc.listen()
|
|
500
665
|
return itc
|
|
501
666
|
}
|
|
667
|
+
|
|
668
|
+
function wrapDispatchTarget (target, run) {
|
|
669
|
+
if (typeof target.inject === 'function') {
|
|
670
|
+
return {
|
|
671
|
+
inject: (req, callback) => run(req, () => target.inject(req, callback)),
|
|
672
|
+
server: target.server,
|
|
673
|
+
emit: target.emit?.bind(target),
|
|
674
|
+
listenerCount: target.listenerCount?.bind(target)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (typeof target.emit === 'function') {
|
|
679
|
+
return target
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return target
|
|
683
|
+
}
|