Skip to content

Commit 6e8792b

Browse files
metcoder95Copilot
authored andcommitted
refactor: small adjustments over WorkerInfo (#860)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> (cherry picked from commit 48bf5fa)
1 parent 3c1abe9 commit 6e8792b

2 files changed

Lines changed: 108 additions & 90 deletions

File tree

src/index.ts

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -231,31 +231,40 @@ class ThreadPool {
231231
}
232232

233233
_addNewWorker () : void {
234-
if (this.closingUp) return;
235-
236-
const pool = this;
237-
const worker = new Worker(resolve(__dirname, 'worker.js'), {
238-
env: this.options.env,
239-
argv: this.options.argv,
240-
execArgv: this.options.execArgv,
241-
resourceLimits: this.options.resourceLimits,
242-
workerData: this.options.workerData,
243-
trackUnmanagedFds: this.options.trackUnmanagedFds
244-
});
234+
if (this.closingUp === true) return;
245235

246236
const { port1, port2 } = new MessageChannel();
247237
const workerInfo = new WorkerInfo({
248-
worker,
249-
onMessage,
238+
worker: {
239+
filename: resolve(__dirname, 'worker.js'),
240+
env: this.options.env,
241+
argv: this.options.argv,
242+
execArgv: this.options.execArgv,
243+
resourceLimits: this.options.resourceLimits,
244+
workerData: this.options.workerData,
245+
trackUnmanagedFds: this.options.trackUnmanagedFds
246+
},
250247
port: port1,
251248
enableHistogram: this.options.workerHistogram
252-
});
253-
249+
}, onMessage.bind(this));
250+
const message : StartupMessage = {
251+
filename: this.options.filename,
252+
name: this.options.name,
253+
port: port2,
254+
sharedBuffer: workerInfo.sharedBuffer,
255+
atomics: this.options.atomics!,
256+
niceIncrement: this.options.niceIncrement
257+
};
258+
254259
workerInfo.onDestroy(() => {
255260
this.publicInterface.emit('workerDestroy', workerInfo.interface);
256261
});
262+
workerInfo.onWorkerMessage(onWorkerMessage.bind(this));
263+
workerInfo.onWorkerError(onWorkerError.bind(this));
264+
workerInfo.onWorkerExit(onWorkerExit.bind(this));
265+
workerInfo.onPortClose(() => { workerInfo.workerRef(); });
257266

258-
if (this.startingUp) {
267+
if (this.startingUp === true) {
259268
// There is no point in waiting for the initial set of Workers to indicate
260269
// that they are ready, we just mark them as such from the start.
261270
workerInfo.markAsReady();
@@ -271,74 +280,55 @@ class ThreadPool {
271280
this._onWorkerReady(workerInfo);
272281
});
273282
}
283+
284+
workerInfo.init(message, [port2]).workerUnref();
285+
this.workers.add(workerInfo);
274286

275-
const message : StartupMessage = {
276-
filename: this.options.filename,
277-
name: this.options.name,
278-
port: port2,
279-
sharedBuffer: workerInfo.sharedBuffer,
280-
atomics: this.options.atomics!,
281-
niceIncrement: this.options.niceIncrement
282-
};
283-
worker.postMessage(message, [port2]);
284-
285-
function onMessage (message : ResponseMessage) {
287+
function onMessage (this: ThreadPool, message : ResponseMessage) {
286288
const { taskId, result } = message;
287289
// In case of success: Call the callback that was passed to `runTask`,
288290
// remove the `TaskInfo` associated with the Worker, which marks it as
289291
// free again.
290292
const taskInfo = workerInfo.popTask(taskId);
291-
pool.workers.taskDone(workerInfo);
293+
this.workers.taskDone(workerInfo);
292294

293-
/* istanbul ignore if */
294-
if (taskInfo == null) {
295+
296+
if (taskInfo == null) { /* c8 ignore next */
295297
const err = new Error(
296298
`Unexpected message from Worker: ${inspect(message)}`);
297-
pool.publicInterface.emit('error', err);
299+
this.publicInterface.emit('error', err);
298300
} else {
299301
taskInfo.done(message.error, result);
300302
}
301303

302-
pool._processPendingMessages();
304+
this._processPendingMessages();
303305
}
304306

305-
function onReady () {
307+
function onWorkerReady () {
306308
workerInfo.currentUsage() === 0 && workerInfo.unref();
307309
workerInfo.isReady() === false && workerInfo.markAsReady();
308310
}
309311

310-
function onEventMessage (message: any) {
311-
pool.publicInterface.emit('message', message);
312+
function onEventMessage (this: ThreadPool, message: any) {
313+
this.publicInterface.emit('message', message);
312314
}
313315

314-
worker.on('message', (message : any) => {
315-
message instanceof Object && READY in message ? onReady() : onEventMessage(message);
316-
});
316+
function onWorkerMessage (this: ThreadPool, message: any) {
317+
message instanceof Object && READY in message ? onWorkerReady() : onEventMessage.call(this, message);
318+
}
317319

318-
worker.on('error', (err : Error) => {
320+
function onWorkerError (this: ThreadPool, err: Error) {
319321
this._onError(workerInfo, err, false);
320-
});
322+
}
321323

322-
worker.on('exit', (exitCode : number) => {
323-
if (this.destroying) {
324-
return;
324+
function onWorkerExit (this: ThreadPool, code: number) {
325+
if (this.destroying === false) {
326+
const err = new Error(`worker exited with code: ${code}`);
327+
// Only error unfinished tasks on process exit, since there are legitimate
328+
// reasons to exit workers and we want to handle that gracefully when possible.
329+
this._onError(workerInfo, err, true);
325330
}
326-
327-
const err = new Error(`worker exited with code: ${exitCode}`);
328-
// Only error unfinished tasks on process exit, since there are legitimate
329-
// reasons to exit workers and we want to handle that gracefully when possible.
330-
this._onError(workerInfo, err, true);
331-
});
332-
333-
worker.unref();
334-
port1.on('close', () => {
335-
// The port is only closed if the Worker stops for some reason, but we
336-
// always .unref() the Worker itself. We want to receive e.g. 'error'
337-
// events on it, so we ref it once we know it's going to exit anyway.
338-
worker.ref();
339-
});
340-
341-
this.workers.add(workerInfo);
331+
}
342332
}
343333

344334
_onError (workerInfo: WorkerInfo, err: Error, onlyErrorUnfinishedTasks: boolean) {

src/worker_pool/index.ts

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Worker, MessagePort, receiveMessageOnPort } from 'node:worker_threads';
1+
import { Worker, MessagePort, receiveMessageOnPort, WorkerOptions, Transferable } from 'node:worker_threads';
22
import { createHistogram, RecordableHistogram } from 'node:perf_hooks';
33
import assert from 'node:assert';
44

5-
import { RequestMessage, ResponseMessage } from '../types';
5+
import { RequestMessage, ResponseMessage, StartupMessage } from '../types';
66
import { Errors } from '../errors';
77

88
import { TaskInfo } from '../task_queue';
@@ -25,9 +25,10 @@ export type PiscinaWorker = {
2525
}
2626

2727
type WorkerInfoParams = {
28-
worker: Worker,
28+
worker: {
29+
filename: string,
30+
} & WorkerOptions,
2931
port: MessagePort,
30-
onMessage: ResponseCallback,
3132
enableHistogram: boolean,
3233
}
3334

@@ -47,16 +48,16 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
4748
{
4849
worker,
4950
port,
50-
onMessage,
5151
enableHistogram
52-
}: WorkerInfoParams
52+
}: WorkerInfoParams,
53+
onMessage: ResponseCallback
5354
) {
5455
super();
55-
this.worker = worker;
56+
const { filename, ...workerOpts } = worker;
57+
this.worker = new Worker(filename, workerOpts);
5658
this.port = port;
57-
this.port.on('message',
58-
(message : ResponseMessage) => this._handleResponse(message));
5959
this.onMessage = onMessage;
60+
this.port.on('message', this._handleResponse.bind(this));
6061
this.taskInfos = new Map();
6162
this.sharedBuffer = new Int32Array(
6263
new SharedArrayBuffer(kFieldCount * Int32Array.BYTES_PER_ELEMENT));
@@ -67,6 +68,37 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
6768
return this.worker.threadId;
6869
}
6970

71+
onWorkerMessage(handler: (msg: any) => void): void {
72+
this.worker.on('message', handler);
73+
}
74+
75+
onWorkerError(handler: (err: Error) => void): void {
76+
this.worker.on('error', handler);
77+
}
78+
79+
onWorkerExit(handler: (code: number) => void): void {
80+
this.worker.on('exit', handler);
81+
}
82+
83+
onPortClose(handler: () => void): void {
84+
this.port.on('close', handler);
85+
}
86+
87+
init(msg: StartupMessage, toTransfer: Transferable[]): WorkerInfo {
88+
this.worker.postMessage(msg, toTransfer);
89+
return this;
90+
}
91+
92+
workerRef(): WorkerInfo {
93+
this.worker.ref();
94+
return this;
95+
}
96+
97+
workerUnref(): WorkerInfo {
98+
this.worker.unref();
99+
return this;
100+
}
101+
70102
destroy () : void {
71103
if (this.terminating || this.destroyed) return;
72104

@@ -101,16 +133,13 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
101133
}
102134

103135
unref () : WorkerInfo {
104-
// Note: Do not call ref()/unref() on the Worker itself since that may cause
105-
// a hard crash, see https://github.com/nodejs/node/pull/33394.
106136
this.port.unref();
107137
return this;
108138
}
109139

110140
_handleResponse (message : ResponseMessage) : void {
111-
if (message.time != null) {
112-
this.histogram?.record(PiscinaHistogramHandler.toHistogramIntegerNano(message.time));
113-
}
141+
// Both cannot be in different state if histogram enabled.
142+
this.histogram?.record(PiscinaHistogramHandler.toHistogramIntegerNano(message?.time!));
114143

115144
this.onMessage(message);
116145

@@ -122,7 +151,9 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
122151
}
123152

124153
postTask (taskInfo : TaskInfo) {
154+
// Avoid duplicates
125155
assert(!this.taskInfos.has(taskInfo.taskId));
156+
// Avoid posting when pool is shutting down or worker already destroyed
126157
assert(!this.terminating && !this.destroyed);
127158

128159
const message : RequestMessage = {
@@ -135,22 +166,20 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
135166

136167
try {
137168
this.port.postMessage(message, taskInfo.transferList);
169+
queueMicrotask(() => this.clearIdleTimeout())
170+
taskInfo.workerInfo = this;
171+
this.taskInfos.set(taskInfo.taskId, taskInfo);
172+
this.ref();
173+
174+
// Inform the worker that there are new messages posted, and wake it up
175+
// if it is waiting for one.
176+
Atomics.add(this.sharedBuffer, kRequestCountField, 1);
177+
Atomics.notify(this.sharedBuffer, kRequestCountField, 1);
138178
} catch (err) {
139179
// This would mostly happen if e.g. message contains unserializable data
140180
// or transferList is invalid.
141181
taskInfo.done(<Error>err);
142-
return;
143182
}
144-
145-
taskInfo.workerInfo = this;
146-
this.taskInfos.set(taskInfo.taskId, taskInfo);
147-
queueMicrotask(() => this.clearIdleTimeout())
148-
this.ref();
149-
150-
// Inform the worker that there are new messages posted, and wake it up
151-
// if it is waiting for one.
152-
Atomics.add(this.sharedBuffer, kRequestCountField, 1);
153-
Atomics.notify(this.sharedBuffer, kRequestCountField, 1);
154183
}
155184

156185
processPendingMessages () {
@@ -166,7 +195,7 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
166195
this.lastSeenResponseCount = actualResponseCount;
167196

168197
let entry;
169-
while ((entry = receiveMessageOnPort(this.port)) !== undefined) {
198+
while ((entry = receiveMessageOnPort(this.port)) != null) {
170199
this._handleResponse(entry.message);
171200
}
172201
}
@@ -176,18 +205,17 @@ export class WorkerInfo extends AsynchronouslyCreatedResource {
176205
// If there are abortable tasks, we are running one at most per Worker.
177206
if (this.taskInfos.size !== 1) return false;
178207
const [[, task]] = this.taskInfos;
179-
return task.abortSignal !== null;
208+
return task.abortSignal != null;
180209
}
181210

182211
currentUsage () : number {
183-
if (this.isRunningAbortableTask()) return Infinity;
184-
return this.taskInfos.size;
212+
return this.isRunningAbortableTask() ? Infinity : this.taskInfos.size;
185213
}
186214

187215
popTask (taskId: number) : TaskInfo | null {
188216
const task = this.taskInfos.get(taskId) ?? null;
189-
190-
this.taskInfos.delete(taskId);
217+
218+
if (task != null) this.taskInfos.delete(taskId);
191219

192220
return task;
193221
}

0 commit comments

Comments
 (0)