nmtjs 0.16.0 → 0.17.0-beta.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/LICENSE.md +1 -1
- package/README.md +41 -1
- package/dist/index.d.ts +22 -121
- package/dist/index.js +24 -53
- package/dist/index.js.map +1 -1
- package/package.json +11 -63
- package/src/index.ts +71 -91
- package/bin/cli.js +0 -2
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -137
- package/dist/cli.js.map +0 -1
- package/dist/codegen.d.ts +0 -5
- package/dist/codegen.js +0 -18
- package/dist/codegen.js.map +0 -1
- package/dist/config.d.ts +0 -47
- package/dist/config.js +0 -16
- package/dist/config.js.map +0 -1
- package/dist/entrypoints/cli.d.ts +0 -1
- package/dist/entrypoints/cli.js +0 -12
- package/dist/entrypoints/cli.js.map +0 -1
- package/dist/entrypoints/main.d.ts +0 -5
- package/dist/entrypoints/main.js +0 -248
- package/dist/entrypoints/main.js.map +0 -1
- package/dist/entrypoints/thread.d.ts +0 -16
- package/dist/entrypoints/thread.js +0 -182
- package/dist/entrypoints/thread.js.map +0 -1
- package/dist/entrypoints/worker.d.ts +0 -9
- package/dist/entrypoints/worker.js +0 -143
- package/dist/entrypoints/worker.js.map +0 -1
- package/dist/resolver.d.ts +0 -2
- package/dist/resolver.js +0 -7
- package/dist/resolver.js.map +0 -1
- package/dist/runtime/application/index.d.ts +0 -1
- package/dist/runtime/application/index.js +0 -2
- package/dist/runtime/application/index.js.map +0 -1
- package/dist/runtime/constants.d.ts +0 -8
- package/dist/runtime/constants.js +0 -5
- package/dist/runtime/constants.js.map +0 -1
- package/dist/runtime/enums.d.ts +0 -10
- package/dist/runtime/enums.js +0 -13
- package/dist/runtime/enums.js.map +0 -1
- package/dist/runtime/hooks.d.ts +0 -1
- package/dist/runtime/hooks.js +0 -2
- package/dist/runtime/hooks.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -28
- package/dist/runtime/index.js +0 -29
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/injectables.d.ts +0 -28
- package/dist/runtime/injectables.js +0 -24
- package/dist/runtime/injectables.js.map +0 -1
- package/dist/runtime/jobs/job.d.ts +0 -146
- package/dist/runtime/jobs/job.js +0 -89
- package/dist/runtime/jobs/job.js.map +0 -1
- package/dist/runtime/jobs/manager.d.ts +0 -131
- package/dist/runtime/jobs/manager.js +0 -309
- package/dist/runtime/jobs/manager.js.map +0 -1
- package/dist/runtime/jobs/router.d.ts +0 -296
- package/dist/runtime/jobs/router.js +0 -458
- package/dist/runtime/jobs/router.js.map +0 -1
- package/dist/runtime/jobs/runner.d.ts +0 -103
- package/dist/runtime/jobs/runner.js +0 -425
- package/dist/runtime/jobs/runner.js.map +0 -1
- package/dist/runtime/jobs/step.d.ts +0 -26
- package/dist/runtime/jobs/step.js +0 -18
- package/dist/runtime/jobs/step.js.map +0 -1
- package/dist/runtime/jobs/types.d.ts +0 -86
- package/dist/runtime/jobs/types.js +0 -5
- package/dist/runtime/jobs/types.js.map +0 -1
- package/dist/runtime/metrics/metric.d.ts +0 -8
- package/dist/runtime/metrics/metric.js +0 -11
- package/dist/runtime/metrics/metric.js.map +0 -1
- package/dist/runtime/metrics/registry.d.ts +0 -3
- package/dist/runtime/metrics/registry.js +0 -5
- package/dist/runtime/metrics/registry.js.map +0 -1
- package/dist/runtime/metrics/server.d.ts +0 -7
- package/dist/runtime/metrics/server.js +0 -53
- package/dist/runtime/metrics/server.js.map +0 -1
- package/dist/runtime/plugin.d.ts +0 -2
- package/dist/runtime/plugin.js +0 -2
- package/dist/runtime/plugin.js.map +0 -1
- package/dist/runtime/runtime.d.ts +0 -27
- package/dist/runtime/runtime.js +0 -77
- package/dist/runtime/runtime.js.map +0 -1
- package/dist/runtime/scheduler/index.d.ts +0 -22
- package/dist/runtime/scheduler/index.js +0 -20
- package/dist/runtime/scheduler/index.js.map +0 -1
- package/dist/runtime/server/applications.d.ts +0 -91
- package/dist/runtime/server/applications.js +0 -171
- package/dist/runtime/server/applications.js.map +0 -1
- package/dist/runtime/server/config.d.ts +0 -148
- package/dist/runtime/server/config.js +0 -33
- package/dist/runtime/server/config.js.map +0 -1
- package/dist/runtime/server/environment.d.ts +0 -43
- package/dist/runtime/server/environment.js +0 -33
- package/dist/runtime/server/environment.js.map +0 -1
- package/dist/runtime/server/error-policy.d.ts +0 -62
- package/dist/runtime/server/error-policy.js +0 -73
- package/dist/runtime/server/error-policy.js.map +0 -1
- package/dist/runtime/server/hmr-coordinator.d.ts +0 -37
- package/dist/runtime/server/hmr-coordinator.js +0 -94
- package/dist/runtime/server/hmr-coordinator.js.map +0 -1
- package/dist/runtime/server/jobs.d.ts +0 -54
- package/dist/runtime/server/jobs.js +0 -176
- package/dist/runtime/server/jobs.js.map +0 -1
- package/dist/runtime/server/lifecycle.d.ts +0 -85
- package/dist/runtime/server/lifecycle.js +0 -177
- package/dist/runtime/server/lifecycle.js.map +0 -1
- package/dist/runtime/server/managed-worker.d.ts +0 -158
- package/dist/runtime/server/managed-worker.js +0 -420
- package/dist/runtime/server/managed-worker.js.map +0 -1
- package/dist/runtime/server/pool-manager.d.ts +0 -85
- package/dist/runtime/server/pool-manager.js +0 -129
- package/dist/runtime/server/pool-manager.js.map +0 -1
- package/dist/runtime/server/proxy.d.ts +0 -21
- package/dist/runtime/server/proxy.js +0 -76
- package/dist/runtime/server/proxy.js.map +0 -1
- package/dist/runtime/server/server.d.ts +0 -60
- package/dist/runtime/server/server.js +0 -163
- package/dist/runtime/server/server.js.map +0 -1
- package/dist/runtime/server/types.d.ts +0 -31
- package/dist/runtime/server/types.js +0 -2
- package/dist/runtime/server/types.js.map +0 -1
- package/dist/runtime/server/worker-pool.d.ts +0 -121
- package/dist/runtime/server/worker-pool.js +0 -171
- package/dist/runtime/server/worker-pool.js.map +0 -1
- package/dist/runtime/store/index.d.ts +0 -3
- package/dist/runtime/store/index.js +0 -23
- package/dist/runtime/store/index.js.map +0 -1
- package/dist/runtime/subscription/manager.d.ts +0 -49
- package/dist/runtime/subscription/manager.js +0 -170
- package/dist/runtime/subscription/manager.js.map +0 -1
- package/dist/runtime/subscription/redis.d.ts +0 -18
- package/dist/runtime/subscription/redis.js +0 -120
- package/dist/runtime/subscription/redis.js.map +0 -1
- package/dist/runtime/types.d.ts +0 -95
- package/dist/runtime/types.js +0 -2
- package/dist/runtime/types.js.map +0 -1
- package/dist/runtime/workers/application.d.ts +0 -29
- package/dist/runtime/workers/application.js +0 -77
- package/dist/runtime/workers/application.js.map +0 -1
- package/dist/runtime/workers/base.d.ts +0 -16
- package/dist/runtime/workers/base.js +0 -46
- package/dist/runtime/workers/base.js.map +0 -1
- package/dist/runtime/workers/cli.d.ts +0 -1
- package/dist/runtime/workers/cli.js +0 -2
- package/dist/runtime/workers/cli.js.map +0 -1
- package/dist/runtime/workers/job.d.ts +0 -20
- package/dist/runtime/workers/job.js +0 -170
- package/dist/runtime/workers/job.js.map +0 -1
- package/dist/vite/builder.d.ts +0 -5
- package/dist/vite/builder.js +0 -79
- package/dist/vite/builder.js.map +0 -1
- package/dist/vite/config.d.ts +0 -28
- package/dist/vite/config.js +0 -22
- package/dist/vite/config.js.map +0 -1
- package/dist/vite/plugins.d.ts +0 -2
- package/dist/vite/plugins.js +0 -43
- package/dist/vite/plugins.js.map +0 -1
- package/dist/vite/runners/worker.d.ts +0 -3
- package/dist/vite/runners/worker.js +0 -34
- package/dist/vite/runners/worker.js.map +0 -1
- package/dist/vite/server.d.ts +0 -3
- package/dist/vite/server.js +0 -36
- package/dist/vite/server.js.map +0 -1
- package/dist/vite/servers/main.d.ts +0 -8
- package/dist/vite/servers/main.js +0 -24
- package/dist/vite/servers/main.js.map +0 -1
- package/dist/vite/servers/worker.d.ts +0 -13
- package/dist/vite/servers/worker.js +0 -124
- package/dist/vite/servers/worker.js.map +0 -1
- package/src/cli.ts +0 -162
- package/src/codegen.ts +0 -30
- package/src/config.ts +0 -69
- package/src/entrypoints/cli.ts +0 -13
- package/src/entrypoints/main.ts +0 -317
- package/src/entrypoints/thread.ts +0 -223
- package/src/entrypoints/worker.ts +0 -174
- package/src/resolver.ts +0 -7
- package/src/runtime/application/index.ts +0 -1
- package/src/runtime/constants.ts +0 -13
- package/src/runtime/enums.ts +0 -12
- package/src/runtime/hooks.ts +0 -1
- package/src/runtime/index.ts +0 -29
- package/src/runtime/injectables.ts +0 -72
- package/src/runtime/jobs/job.ts +0 -467
- package/src/runtime/jobs/manager.ts +0 -454
- package/src/runtime/jobs/router.ts +0 -967
- package/src/runtime/jobs/runner.ts +0 -583
- package/src/runtime/jobs/step.ts +0 -66
- package/src/runtime/jobs/types.ts +0 -116
- package/src/runtime/metrics/metric.ts +0 -18
- package/src/runtime/metrics/registry.ts +0 -11
- package/src/runtime/metrics/server.ts +0 -69
- package/src/runtime/plugin.ts +0 -2
- package/src/runtime/runtime.ts +0 -103
- package/src/runtime/scheduler/index.ts +0 -39
- package/src/runtime/server/applications.ts +0 -264
- package/src/runtime/server/config.ts +0 -199
- package/src/runtime/server/environment.ts +0 -71
- package/src/runtime/server/error-policy.ts +0 -132
- package/src/runtime/server/hmr-coordinator.ts +0 -116
- package/src/runtime/server/jobs.ts +0 -266
- package/src/runtime/server/lifecycle.ts +0 -234
- package/src/runtime/server/managed-worker.ts +0 -546
- package/src/runtime/server/pool-manager.ts +0 -187
- package/src/runtime/server/proxy.ts +0 -119
- package/src/runtime/server/server.ts +0 -218
- package/src/runtime/server/types.ts +0 -32
- package/src/runtime/server/worker-pool.ts +0 -275
- package/src/runtime/store/index.ts +0 -30
- package/src/runtime/subscription/manager.ts +0 -310
- package/src/runtime/subscription/redis.ts +0 -157
- package/src/runtime/types.ts +0 -84
- package/src/runtime/workers/application.ts +0 -133
- package/src/runtime/workers/base.ts +0 -71
- package/src/runtime/workers/cli.ts +0 -0
- package/src/runtime/workers/job.ts +0 -142
- package/src/vite/builder.ts +0 -94
- package/src/vite/config.ts +0 -45
- package/src/vite/plugins.ts +0 -53
- package/src/vite/runners/worker.ts +0 -55
- package/src/vite/server.ts +0 -55
- package/src/vite/servers/main.ts +0 -41
- package/src/vite/servers/worker.ts +0 -177
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
import type { MessagePort, WorkerOptions } from 'node:worker_threads'
|
|
2
|
-
import EventEmitter, { once } from 'node:events'
|
|
3
|
-
import { MessageChannel, Worker } from 'node:worker_threads'
|
|
4
|
-
|
|
5
|
-
import type { Logger } from '@nmtjs/core'
|
|
6
|
-
|
|
7
|
-
import type { WorkerType } from '../enums.ts'
|
|
8
|
-
import type {
|
|
9
|
-
JobTaskResult,
|
|
10
|
-
ServerPortMessageTypes,
|
|
11
|
-
ThreadErrorMessage,
|
|
12
|
-
ThreadPortMessage,
|
|
13
|
-
ThreadPortMessageTypes,
|
|
14
|
-
WorkerJobTask,
|
|
15
|
-
WorkerThreadError,
|
|
16
|
-
} from '../types.ts'
|
|
17
|
-
import type { ErrorPolicy, WorkerErrorContext } from './error-policy.ts'
|
|
18
|
-
|
|
19
|
-
const omitExecArgv = ['--expose-gc']
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* States in the worker state machine.
|
|
23
|
-
*/
|
|
24
|
-
export type WorkerState =
|
|
25
|
-
| 'idle'
|
|
26
|
-
| 'starting'
|
|
27
|
-
| 'ready'
|
|
28
|
-
| 'error'
|
|
29
|
-
| 'restarting'
|
|
30
|
-
| 'stopping'
|
|
31
|
-
| 'stopped'
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Context tracked for each managed worker.
|
|
35
|
-
*/
|
|
36
|
-
export interface ManagedWorkerContext {
|
|
37
|
-
consecutiveFailures: number
|
|
38
|
-
totalFailures: number
|
|
39
|
-
lastStableTime: number | null
|
|
40
|
-
lastError: Error | null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Configuration for creating a managed worker.
|
|
45
|
-
*/
|
|
46
|
-
export interface ManagedWorkerConfig {
|
|
47
|
-
id: string
|
|
48
|
-
name: string
|
|
49
|
-
index: number
|
|
50
|
-
workerType: WorkerType
|
|
51
|
-
path: string
|
|
52
|
-
workerData?: any
|
|
53
|
-
workerOptions?: Partial<WorkerOptions>
|
|
54
|
-
onWorker?: (registration: { worker: Worker; vitePort?: MessagePort }) => void
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Events emitted by ManagedWorker.
|
|
59
|
-
*/
|
|
60
|
-
export interface ManagedWorkerEvents {
|
|
61
|
-
'state-change': [from: WorkerState, to: WorkerState]
|
|
62
|
-
ready: [hosts?: ThreadPortMessageTypes['ready']['hosts']]
|
|
63
|
-
error: [error: WorkerThreadError]
|
|
64
|
-
task: [data: ThreadPortMessageTypes['task']]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Time window (ms) after which a worker is considered "stable".
|
|
69
|
-
* Used to reset consecutive failure count.
|
|
70
|
-
*/
|
|
71
|
-
const STABILITY_WINDOW_MS = 30_000
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Worker startup timeout (ms).
|
|
75
|
-
*/
|
|
76
|
-
const STARTUP_TIMEOUT_MS = 15_000
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Worker stop timeout (ms).
|
|
80
|
-
*/
|
|
81
|
-
const STOP_TIMEOUT_MS = 10_000
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* ManagedWorker wraps a Node.js Worker thread with:
|
|
85
|
-
* - Explicit state machine
|
|
86
|
-
* - Automatic restart with exponential backoff
|
|
87
|
-
* - Error tracking and stability detection
|
|
88
|
-
* - Integration with ErrorPolicy for restart decisions
|
|
89
|
-
*
|
|
90
|
-
* State Machine:
|
|
91
|
-
* - idle: Initial state, worker not created
|
|
92
|
-
* - starting: Worker created, waiting for ready message
|
|
93
|
-
* - ready: Worker is running and healthy
|
|
94
|
-
* - error: Worker crashed or failed to start
|
|
95
|
-
* - restarting: Worker is scheduled for restart (waiting for delay)
|
|
96
|
-
* - stopping: Worker is being terminated
|
|
97
|
-
* - stopped: Worker has been terminated (terminal state)
|
|
98
|
-
*/
|
|
99
|
-
export class ManagedWorker extends EventEmitter<ManagedWorkerEvents> {
|
|
100
|
-
protected state: WorkerState = 'idle'
|
|
101
|
-
protected ctx: ManagedWorkerContext = {
|
|
102
|
-
consecutiveFailures: 0,
|
|
103
|
-
totalFailures: 0,
|
|
104
|
-
lastStableTime: null,
|
|
105
|
-
lastError: null,
|
|
106
|
-
}
|
|
107
|
-
protected worker: Worker | null = null
|
|
108
|
-
protected port: MessagePort | null = null
|
|
109
|
-
protected vitePort: MessagePort | null = null
|
|
110
|
-
protected restartTimer: ReturnType<typeof setTimeout> | null = null
|
|
111
|
-
protected startPromise: Promise<void> | null = null
|
|
112
|
-
protected pendingTaskIds = new Set<string>()
|
|
113
|
-
protected logger: Logger
|
|
114
|
-
|
|
115
|
-
constructor(
|
|
116
|
-
readonly config: ManagedWorkerConfig,
|
|
117
|
-
protected readonly policy: ErrorPolicy,
|
|
118
|
-
logger: Logger,
|
|
119
|
-
) {
|
|
120
|
-
super()
|
|
121
|
-
this.logger = logger.child({
|
|
122
|
-
component: 'ManagedWorker',
|
|
123
|
-
workerId: config.id,
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Current worker state */
|
|
128
|
-
get currentState(): WorkerState {
|
|
129
|
-
return this.state
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Whether the worker is healthy (ready state) */
|
|
133
|
-
get isHealthy(): boolean {
|
|
134
|
-
return this.state === 'ready'
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Get a copy of the worker context */
|
|
138
|
-
get context(): ManagedWorkerContext {
|
|
139
|
-
return { ...this.ctx }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Reset failure counts. Called when HMR update comes in to give the worker
|
|
144
|
-
* a fresh start after code has been fixed.
|
|
145
|
-
*/
|
|
146
|
-
resetFailureCount(): void {
|
|
147
|
-
this.ctx.consecutiveFailures = 0
|
|
148
|
-
this.ctx.lastError = null
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Get the underlying Worker instance (if exists) */
|
|
152
|
-
getWorker(): Worker | null {
|
|
153
|
-
return this.worker
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Start the worker.
|
|
158
|
-
* Can only be called from 'idle' or 'error' states.
|
|
159
|
-
*/
|
|
160
|
-
async start(): Promise<void> {
|
|
161
|
-
if (this.state === 'ready') return
|
|
162
|
-
if (this.startPromise) return this.startPromise
|
|
163
|
-
this.assertState('idle', 'error')
|
|
164
|
-
|
|
165
|
-
this.startPromise = this.doStart()
|
|
166
|
-
try {
|
|
167
|
-
await this.startPromise
|
|
168
|
-
} finally {
|
|
169
|
-
this.startPromise = null
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Stop the worker.
|
|
175
|
-
* Can be called from any state except 'stopped'.
|
|
176
|
-
* Clears any pending restart timers and rejects pending run() tasks.
|
|
177
|
-
*/
|
|
178
|
-
async stop(): Promise<void> {
|
|
179
|
-
this.clearRestartTimer()
|
|
180
|
-
|
|
181
|
-
if (this.state === 'stopped') return
|
|
182
|
-
this.transition('stopping')
|
|
183
|
-
|
|
184
|
-
// Reject any pending run() calls that are waiting for task results.
|
|
185
|
-
// Without this, callers of pool.run() would hang forever since the
|
|
186
|
-
// port is about to be closed and task-* events will never fire.
|
|
187
|
-
this.rejectPendingTasks()
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
await this.terminateWorker()
|
|
191
|
-
} finally {
|
|
192
|
-
this.cleanup()
|
|
193
|
-
this.transition('stopped')
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Run a task on this worker (for job workers).
|
|
199
|
-
* The worker must be in 'ready' state.
|
|
200
|
-
*/
|
|
201
|
-
async run(task: WorkerJobTask): Promise<JobTaskResult> {
|
|
202
|
-
if (this.state !== 'ready' || !this.port) {
|
|
203
|
-
throw new Error('Worker is not ready to run tasks')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const id = crypto.randomUUID()
|
|
207
|
-
this.pendingTaskIds.add(id)
|
|
208
|
-
this.send('task', { id, task })
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const [result] = (await once(this, `task-${id}` as any)) as [
|
|
212
|
-
JobTaskResult,
|
|
213
|
-
]
|
|
214
|
-
return result
|
|
215
|
-
} finally {
|
|
216
|
-
this.pendingTaskIds.delete(id)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Send a message to the worker.
|
|
222
|
-
*/
|
|
223
|
-
send<T extends keyof ServerPortMessageTypes>(
|
|
224
|
-
type: T,
|
|
225
|
-
...[data]: ServerPortMessageTypes[T] extends undefined
|
|
226
|
-
? []
|
|
227
|
-
: [data: ServerPortMessageTypes[T]]
|
|
228
|
-
): void {
|
|
229
|
-
this.port?.postMessage({ type, data })
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Internal: Start the worker and wait for it to be ready.
|
|
234
|
-
*/
|
|
235
|
-
protected async doStart(): Promise<void> {
|
|
236
|
-
this.transition('starting')
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const { port1, port2 } = new MessageChannel()
|
|
240
|
-
this.port = port1
|
|
241
|
-
|
|
242
|
-
const viteChannel = this.config.onWorker ? new MessageChannel() : null
|
|
243
|
-
this.vitePort = viteChannel?.port1 ?? null
|
|
244
|
-
|
|
245
|
-
const { config } = this
|
|
246
|
-
this.worker = new Worker(config.path, {
|
|
247
|
-
...config.workerOptions,
|
|
248
|
-
execArgv: process.execArgv.filter((f) => !omitExecArgv.includes(f)),
|
|
249
|
-
workerData: {
|
|
250
|
-
...config.workerData,
|
|
251
|
-
port: port2,
|
|
252
|
-
vitePort: viteChannel?.port2,
|
|
253
|
-
},
|
|
254
|
-
name: `${config.name}-${config.index + 1}`,
|
|
255
|
-
transferList: viteChannel ? [port2, viteChannel.port2] : [port2],
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
// Notify callback if provided
|
|
259
|
-
config.onWorker?.({
|
|
260
|
-
worker: this.worker,
|
|
261
|
-
vitePort: this.vitePort ?? undefined,
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
// Set up message handling
|
|
265
|
-
this.attachListeners()
|
|
266
|
-
|
|
267
|
-
// Wait for ready with timeout
|
|
268
|
-
await this.waitForReady()
|
|
269
|
-
} catch (error) {
|
|
270
|
-
this.handleError(error as Error)
|
|
271
|
-
throw error
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Wait for the worker to emit a 'ready' message or timeout.
|
|
277
|
-
*/
|
|
278
|
-
protected waitForReady(): Promise<void> {
|
|
279
|
-
return new Promise((resolve, reject) => {
|
|
280
|
-
let settled = false
|
|
281
|
-
let timer: ReturnType<typeof setTimeout>
|
|
282
|
-
|
|
283
|
-
const cleanup = () => {
|
|
284
|
-
if (settled) return
|
|
285
|
-
settled = true
|
|
286
|
-
clearTimeout(timer)
|
|
287
|
-
this.off('ready', handleReady)
|
|
288
|
-
this.off('error', handleError)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const handleReady = () => {
|
|
292
|
-
cleanup()
|
|
293
|
-
resolve()
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const handleError = (error: WorkerThreadError) => {
|
|
297
|
-
cleanup()
|
|
298
|
-
reject(error)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
this.once('ready', handleReady)
|
|
302
|
-
this.once('error', handleError)
|
|
303
|
-
|
|
304
|
-
timer = setTimeout(() => {
|
|
305
|
-
const error = createWorkerThreadError({
|
|
306
|
-
message: `Worker thread ${this.config.id} did not become ready in time`,
|
|
307
|
-
name: 'WorkerStartupTimeoutError',
|
|
308
|
-
origin: 'start',
|
|
309
|
-
fatal: true,
|
|
310
|
-
})
|
|
311
|
-
cleanup()
|
|
312
|
-
this.handleError(error)
|
|
313
|
-
reject(error)
|
|
314
|
-
}, STARTUP_TIMEOUT_MS)
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Attach listeners for port and worker events.
|
|
320
|
-
*/
|
|
321
|
-
protected attachListeners(): void {
|
|
322
|
-
if (!this.port || !this.worker) return
|
|
323
|
-
|
|
324
|
-
this.port.on('message', (msg: ThreadPortMessage) => {
|
|
325
|
-
const { type, data } = msg
|
|
326
|
-
switch (type) {
|
|
327
|
-
case 'ready':
|
|
328
|
-
this.handleReady(data.hosts)
|
|
329
|
-
break
|
|
330
|
-
case 'error': {
|
|
331
|
-
const error = createWorkerThreadError(data as ThreadErrorMessage)
|
|
332
|
-
this.handleError(error)
|
|
333
|
-
break
|
|
334
|
-
}
|
|
335
|
-
case 'task': {
|
|
336
|
-
const taskData = data as ThreadPortMessageTypes['task']
|
|
337
|
-
this.emit('task', taskData)
|
|
338
|
-
this.emit(`task-${taskData.id}` as any, taskData.task)
|
|
339
|
-
break
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
this.worker.once('exit', (code) => {
|
|
345
|
-
// Ignore if we're already stopping/stopped
|
|
346
|
-
if (this.state === 'stopping' || this.state === 'stopped') return
|
|
347
|
-
|
|
348
|
-
const error = createWorkerThreadError({
|
|
349
|
-
message: `Worker thread ${this.config.id} exited unexpectedly with code ${code}`,
|
|
350
|
-
name: 'WorkerThreadExitError',
|
|
351
|
-
origin: 'runtime',
|
|
352
|
-
fatal: code !== 0,
|
|
353
|
-
})
|
|
354
|
-
this.handleError(error)
|
|
355
|
-
})
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Handle worker ready event.
|
|
360
|
-
*/
|
|
361
|
-
protected handleReady(
|
|
362
|
-
hosts?: ThreadPortMessageTypes['ready']['hosts'],
|
|
363
|
-
): void {
|
|
364
|
-
// Check if worker was stable before this (for failure count reset)
|
|
365
|
-
if (
|
|
366
|
-
this.ctx.lastStableTime !== null &&
|
|
367
|
-
Date.now() - this.ctx.lastStableTime > STABILITY_WINDOW_MS
|
|
368
|
-
) {
|
|
369
|
-
this.ctx.consecutiveFailures = 0
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
this.ctx.lastStableTime = Date.now()
|
|
373
|
-
this.transition('ready')
|
|
374
|
-
this.emit('ready', hosts)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Handle worker error.
|
|
379
|
-
*/
|
|
380
|
-
protected handleError(error: Error): void {
|
|
381
|
-
const workerError = error as WorkerThreadError
|
|
382
|
-
this.ctx.lastError = error
|
|
383
|
-
this.ctx.consecutiveFailures++
|
|
384
|
-
this.ctx.totalFailures++
|
|
385
|
-
|
|
386
|
-
this.transition('error')
|
|
387
|
-
if (this.listenerCount('error') > 0) {
|
|
388
|
-
this.emit('error', workerError)
|
|
389
|
-
} else {
|
|
390
|
-
this.logger.error(workerError)
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Unblock any pending run() callers waiting for task-* events.
|
|
394
|
-
// On runtime errors the current worker may never emit task completion.
|
|
395
|
-
this.rejectPendingTasks()
|
|
396
|
-
|
|
397
|
-
// Cleanup old worker
|
|
398
|
-
this.terminateWorker().catch(() => {})
|
|
399
|
-
this.cleanup()
|
|
400
|
-
|
|
401
|
-
// Consult policy for action
|
|
402
|
-
const errorContext: WorkerErrorContext = {
|
|
403
|
-
workerId: this.config.id,
|
|
404
|
-
workerType: this.config.workerType,
|
|
405
|
-
consecutiveFailures: this.ctx.consecutiveFailures,
|
|
406
|
-
totalFailures: this.ctx.totalFailures,
|
|
407
|
-
lastStableTime: this.ctx.lastStableTime,
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const action = this.policy.onWorkerError(workerError, errorContext)
|
|
411
|
-
|
|
412
|
-
switch (action.type) {
|
|
413
|
-
case 'restart':
|
|
414
|
-
this.scheduleRestart(action.delay)
|
|
415
|
-
break
|
|
416
|
-
case 'exit':
|
|
417
|
-
this.logger.fatal(
|
|
418
|
-
new Error('Worker failed, exiting process', { cause: error }),
|
|
419
|
-
)
|
|
420
|
-
process.exit(action.code)
|
|
421
|
-
break
|
|
422
|
-
case 'wait':
|
|
423
|
-
this.logger.debug('Worker failed, waiting for fix via HMR')
|
|
424
|
-
break
|
|
425
|
-
case 'ignore':
|
|
426
|
-
this.logger.warn({ error }, 'Worker error ignored')
|
|
427
|
-
break
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Schedule a restart after the given delay.
|
|
433
|
-
*/
|
|
434
|
-
protected scheduleRestart(delay: number): void {
|
|
435
|
-
this.transition('restarting')
|
|
436
|
-
this.logger.debug(
|
|
437
|
-
{ delay, attempt: this.ctx.consecutiveFailures },
|
|
438
|
-
'Scheduling worker restart',
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
this.restartTimer = setTimeout(() => {
|
|
442
|
-
this.restartTimer = null
|
|
443
|
-
// Reset to idle before starting
|
|
444
|
-
this.transition('idle')
|
|
445
|
-
this.start().catch((error) => {
|
|
446
|
-
this.logger.error(new Error('Restart failed', { cause: error }))
|
|
447
|
-
})
|
|
448
|
-
}, delay)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Clear any pending restart timer.
|
|
453
|
-
*/
|
|
454
|
-
protected clearRestartTimer(): void {
|
|
455
|
-
if (this.restartTimer) {
|
|
456
|
-
clearTimeout(this.restartTimer)
|
|
457
|
-
this.restartTimer = null
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Terminate the underlying worker thread.
|
|
463
|
-
*/
|
|
464
|
-
protected async terminateWorker(): Promise<void> {
|
|
465
|
-
if (!this.worker) return
|
|
466
|
-
|
|
467
|
-
const timeout = STOP_TIMEOUT_MS
|
|
468
|
-
try {
|
|
469
|
-
const signal = AbortSignal.timeout(timeout)
|
|
470
|
-
const exitPromise = once(this.worker, 'exit', { signal })
|
|
471
|
-
this.send('stop')
|
|
472
|
-
this.port?.close()
|
|
473
|
-
await exitPromise
|
|
474
|
-
} catch {
|
|
475
|
-
this.logger.warn(
|
|
476
|
-
`Worker thread ${this.config.id} did not terminate in time, forcing termination`,
|
|
477
|
-
)
|
|
478
|
-
await this.worker.terminate()
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Cleanup internal state.
|
|
484
|
-
*/
|
|
485
|
-
protected cleanup(): void {
|
|
486
|
-
this.port?.close()
|
|
487
|
-
this.vitePort?.close()
|
|
488
|
-
this.worker = null
|
|
489
|
-
this.port = null
|
|
490
|
-
this.vitePort = null
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Reject all pending run() calls by emitting an error result
|
|
495
|
-
* for each tracked task id. This unblocks callers waiting on
|
|
496
|
-
* `once(this, 'task-<id>')` so they don't hang during shutdown.
|
|
497
|
-
*/
|
|
498
|
-
protected rejectPendingTasks(): void {
|
|
499
|
-
for (const id of this.pendingTaskIds) {
|
|
500
|
-
const result: JobTaskResult = {
|
|
501
|
-
type: 'error',
|
|
502
|
-
error: new Error('Worker is shutting down'),
|
|
503
|
-
}
|
|
504
|
-
this.emit(`task-${id}` as any, result)
|
|
505
|
-
}
|
|
506
|
-
this.pendingTaskIds.clear()
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Transition to a new state, emitting the state-change event.
|
|
511
|
-
*/
|
|
512
|
-
protected transition(to: WorkerState): void {
|
|
513
|
-
const from = this.state
|
|
514
|
-
this.state = to
|
|
515
|
-
this.logger.trace({ from, to }, 'Worker state transition')
|
|
516
|
-
this.emit('state-change', from, to)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Assert that the current state is one of the allowed states.
|
|
521
|
-
*/
|
|
522
|
-
protected assertState(...allowed: WorkerState[]): void {
|
|
523
|
-
if (!allowed.includes(this.state)) {
|
|
524
|
-
throw new Error(
|
|
525
|
-
`Invalid worker state: ${this.state}, expected one of: ${allowed.join(', ')}`,
|
|
526
|
-
)
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Create a WorkerThreadError from a message.
|
|
533
|
-
*/
|
|
534
|
-
function createWorkerThreadError(
|
|
535
|
-
message: ThreadErrorMessage,
|
|
536
|
-
includeStack = true,
|
|
537
|
-
): WorkerThreadError {
|
|
538
|
-
const error = new Error(message.message) as WorkerThreadError
|
|
539
|
-
if (message.name) error.name = message.name
|
|
540
|
-
if (includeStack && message.stack) {
|
|
541
|
-
error.stack = message.stack
|
|
542
|
-
}
|
|
543
|
-
error.origin = message.origin
|
|
544
|
-
error.fatal = message.fatal
|
|
545
|
-
return error
|
|
546
|
-
}
|