source: trip-planner-front/node_modules/piscina/dist/src/index.js@ e29cc2e

Last change on this file since e29cc2e was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 33.8 KB
RevLine 
[6a3a178]1"use strict";
2var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
3 if (!privateMap.has(receiver)) {
4 throw new TypeError("attempted to set private field on non-instance");
5 }
6 privateMap.set(receiver, value);
7 return value;
8};
9var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
10 if (!privateMap.has(receiver)) {
11 throw new TypeError("attempted to get private field on non-instance");
12 }
13 return privateMap.get(receiver);
14};
15var __importDefault = (this && this.__importDefault) || function (mod) {
16 return (mod && mod.__esModule) ? mod : { "default": mod };
17};
18var _value, _view, _pool;
19const worker_threads_1 = require("worker_threads");
20const events_1 = require("events");
21const eventemitter_asyncresource_1 = __importDefault(require("eventemitter-asyncresource"));
22const async_hooks_1 = require("async_hooks");
23const os_1 = require("os");
24const url_1 = require("url");
25const path_1 = require("path");
26const util_1 = require("util");
27const assert_1 = __importDefault(require("assert"));
28const hdr_histogram_js_1 = require("hdr-histogram-js");
29const perf_hooks_1 = require("perf_hooks");
30const hdr_histogram_percentiles_obj_1 = __importDefault(require("hdr-histogram-percentiles-obj"));
31const common_1 = require("./common");
32const package_json_1 = require("../package.json");
33const cpuCount = (() => {
34 try {
35 return os_1.cpus().length;
36 }
37 catch {
38 /* istanbul ignore next */
39 return 1;
40 }
41})();
42;
43function onabort(abortSignal, listener) {
44 if ('addEventListener' in abortSignal) {
45 abortSignal.addEventListener('abort', listener, { once: true });
46 }
47 else {
48 abortSignal.once('abort', listener);
49 }
50}
51class AbortError extends Error {
52 constructor() {
53 super('The task has been aborted');
54 }
55 get name() { return 'AbortError'; }
56}
57class ArrayTaskQueue {
58 constructor() {
59 this.tasks = [];
60 }
61 get size() { return this.tasks.length; }
62 shift() {
63 return this.tasks.shift();
64 }
65 push(task) {
66 this.tasks.push(task);
67 }
68 remove(task) {
69 const index = this.tasks.indexOf(task);
70 assert_1.default.notStrictEqual(index, -1);
71 this.tasks.splice(index, 1);
72 }
73}
74const kDefaultOptions = {
75 filename: null,
76 name: 'default',
77 minThreads: Math.max(cpuCount / 2, 1),
78 maxThreads: cpuCount * 1.5,
79 idleTimeout: 0,
80 maxQueue: Infinity,
81 concurrentTasksPerWorker: 1,
82 useAtomics: true,
83 taskQueue: new ArrayTaskQueue(),
84 niceIncrement: 0,
85 trackUnmanagedFds: true
86};
87const kDefaultRunOptions = {
88 transferList: undefined,
89 filename: null,
90 signal: null,
91 name: null
92};
93class DirectlyTransferable {
94 constructor(value) {
95 _value.set(this, void 0);
96 __classPrivateFieldSet(this, _value, value);
97 }
98 get [(_value = new WeakMap(), common_1.kTransferable)]() { return __classPrivateFieldGet(this, _value); }
99 get [common_1.kValue]() { return __classPrivateFieldGet(this, _value); }
100}
101class ArrayBufferViewTransferable {
102 constructor(view) {
103 _view.set(this, void 0);
104 __classPrivateFieldSet(this, _view, view);
105 }
106 get [(_view = new WeakMap(), common_1.kTransferable)]() { return __classPrivateFieldGet(this, _view).buffer; }
107 get [common_1.kValue]() { return __classPrivateFieldGet(this, _view); }
108}
109let taskIdCounter = 0;
110function maybeFileURLToPath(filename) {
111 return filename.startsWith('file:')
112 ? url_1.fileURLToPath(new url_1.URL(filename))
113 : filename;
114}
115// Extend AsyncResource so that async relations between posting a task and
116// receiving its result are visible to diagnostic tools.
117class TaskInfo extends async_hooks_1.AsyncResource {
118 constructor(task, transferList, filename, name, callback, abortSignal, triggerAsyncId) {
119 super('Piscina.Task', { requireManualDestroy: true, triggerAsyncId });
120 this.abortListener = null;
121 this.workerInfo = null;
122 this.callback = callback;
123 this.task = task;
124 this.transferList = transferList;
125 // If the task is a Transferable returned by
126 // Piscina.move(), then add it to the transferList
127 // automatically
128 if (common_1.isMovable(task)) {
129 // This condition should never be hit but typescript
130 // complains if we dont do the check.
131 /* istanbul ignore if */
132 if (this.transferList == null) {
133 this.transferList = [];
134 }
135 this.transferList =
136 this.transferList.concat(task[common_1.kTransferable]);
137 this.task = task[common_1.kValue];
138 }
139 this.filename = filename;
140 this.name = name;
141 this.taskId = taskIdCounter++;
142 this.abortSignal = abortSignal;
143 this.created = perf_hooks_1.performance.now();
144 this.started = 0;
145 }
146 releaseTask() {
147 const ret = this.task;
148 this.task = null;
149 return ret;
150 }
151 done(err, result) {
152 this.runInAsyncScope(this.callback, null, err, result);
153 this.emitDestroy(); // `TaskInfo`s are used only once.
154 // If an abort signal was used, remove the listener from it when
155 // done to make sure we do not accidentally leak.
156 if (this.abortSignal && this.abortListener) {
157 if ('removeEventListener' in this.abortSignal && this.abortListener) {
158 this.abortSignal.removeEventListener('abort', this.abortListener);
159 }
160 else {
161 this.abortSignal.off('abort', this.abortListener);
162 }
163 }
164 }
165 get [common_1.kQueueOptions]() {
166 return common_1.kQueueOptions in this.task ? this.task[common_1.kQueueOptions] : null;
167 }
168}
169class AsynchronouslyCreatedResource {
170 constructor() {
171 this.onreadyListeners = [];
172 }
173 markAsReady() {
174 const listeners = this.onreadyListeners;
175 assert_1.default(listeners !== null);
176 this.onreadyListeners = null;
177 for (const listener of listeners) {
178 listener();
179 }
180 }
181 isReady() {
182 return this.onreadyListeners === null;
183 }
184 onReady(fn) {
185 if (this.onreadyListeners === null) {
186 fn(); // Zalgo is okay here.
187 return;
188 }
189 this.onreadyListeners.push(fn);
190 }
191}
192class AsynchronouslyCreatedResourcePool {
193 constructor(maximumUsage) {
194 this.pendingItems = new Set();
195 this.readyItems = new Set();
196 this.maximumUsage = maximumUsage;
197 this.onAvailableListeners = [];
198 }
199 add(item) {
200 this.pendingItems.add(item);
201 item.onReady(() => {
202 /* istanbul ignore else */
203 if (this.pendingItems.has(item)) {
204 this.pendingItems.delete(item);
205 this.readyItems.add(item);
206 this.maybeAvailable(item);
207 }
208 });
209 }
210 delete(item) {
211 this.pendingItems.delete(item);
212 this.readyItems.delete(item);
213 }
214 findAvailable() {
215 let minUsage = this.maximumUsage;
216 let candidate = null;
217 for (const item of this.readyItems) {
218 const usage = item.currentUsage();
219 if (usage === 0)
220 return item;
221 if (usage < minUsage) {
222 candidate = item;
223 minUsage = usage;
224 }
225 }
226 return candidate;
227 }
228 *[Symbol.iterator]() {
229 yield* this.pendingItems;
230 yield* this.readyItems;
231 }
232 get size() {
233 return this.pendingItems.size + this.readyItems.size;
234 }
235 maybeAvailable(item) {
236 /* istanbul ignore else */
237 if (item.currentUsage() < this.maximumUsage) {
238 for (const listener of this.onAvailableListeners) {
239 listener(item);
240 }
241 }
242 }
243 onAvailable(fn) {
244 this.onAvailableListeners.push(fn);
245 }
246}
247const Errors = {
248 ThreadTermination: () => new Error('Terminating worker thread'),
249 FilenameNotProvided: () => new Error('filename must be provided to run() or in options object'),
250 TaskQueueAtLimit: () => new Error('Task queue is at limit'),
251 NoTaskQueueAvailable: () => new Error('No task queue available and all Workers are busy')
252};
253class WorkerInfo extends AsynchronouslyCreatedResource {
254 constructor(worker, port, onMessage) {
255 super();
256 this.idleTimeout = null; // eslint-disable-line no-undef
257 this.lastSeenResponseCount = 0;
258 this.worker = worker;
259 this.port = port;
260 this.port.on('message', (message) => this._handleResponse(message));
261 this.onMessage = onMessage;
262 this.taskInfos = new Map();
263 this.sharedBuffer = new Int32Array(new SharedArrayBuffer(common_1.kFieldCount * Int32Array.BYTES_PER_ELEMENT));
264 }
265 destroy() {
266 this.worker.terminate();
267 this.port.close();
268 this.clearIdleTimeout();
269 for (const taskInfo of this.taskInfos.values()) {
270 taskInfo.done(Errors.ThreadTermination());
271 }
272 this.taskInfos.clear();
273 }
274 clearIdleTimeout() {
275 if (this.idleTimeout !== null) {
276 clearTimeout(this.idleTimeout);
277 this.idleTimeout = null;
278 }
279 }
280 ref() {
281 this.port.ref();
282 return this;
283 }
284 unref() {
285 // Note: Do not call ref()/unref() on the Worker itself since that may cause
286 // a hard crash, see https://github.com/nodejs/node/pull/33394.
287 this.port.unref();
288 return this;
289 }
290 _handleResponse(message) {
291 this.onMessage(message);
292 if (this.taskInfos.size === 0) {
293 // No more tasks running on this Worker means it should not keep the
294 // process running.
295 this.unref();
296 }
297 }
298 postTask(taskInfo) {
299 assert_1.default(!this.taskInfos.has(taskInfo.taskId));
300 const message = {
301 task: taskInfo.releaseTask(),
302 taskId: taskInfo.taskId,
303 filename: taskInfo.filename,
304 name: taskInfo.name
305 };
306 try {
307 this.port.postMessage(message, taskInfo.transferList);
308 }
309 catch (err) {
310 // This would mostly happen if e.g. message contains unserializable data
311 // or transferList is invalid.
312 taskInfo.done(err);
313 return;
314 }
315 taskInfo.workerInfo = this;
316 this.taskInfos.set(taskInfo.taskId, taskInfo);
317 this.ref();
318 this.clearIdleTimeout();
319 // Inform the worker that there are new messages posted, and wake it up
320 // if it is waiting for one.
321 Atomics.add(this.sharedBuffer, common_1.kRequestCountField, 1);
322 Atomics.notify(this.sharedBuffer, common_1.kRequestCountField, 1);
323 }
324 processPendingMessages() {
325 // If we *know* that there are more messages than we have received using
326 // 'message' events yet, then try to load and handle them synchronously,
327 // without the need to wait for more expensive events on the event loop.
328 // This would usually break async tracking, but in our case, we already have
329 // the extra TaskInfo/AsyncResource layer that rectifies that situation.
330 const actualResponseCount = Atomics.load(this.sharedBuffer, common_1.kResponseCountField);
331 if (actualResponseCount !== this.lastSeenResponseCount) {
332 this.lastSeenResponseCount = actualResponseCount;
333 let entry;
334 while ((entry = worker_threads_1.receiveMessageOnPort(this.port)) !== undefined) {
335 this._handleResponse(entry.message);
336 }
337 }
338 }
339 isRunningAbortableTask() {
340 // If there are abortable tasks, we are running one at most per Worker.
341 if (this.taskInfos.size !== 1)
342 return false;
343 const [[, task]] = this.taskInfos;
344 return task.abortSignal !== null;
345 }
346 currentUsage() {
347 if (this.isRunningAbortableTask())
348 return Infinity;
349 return this.taskInfos.size;
350 }
351}
352class ThreadPool {
353 constructor(publicInterface, options) {
354 var _a;
355 this.skipQueue = [];
356 this.completed = 0;
357 this.start = perf_hooks_1.performance.now();
358 this.inProcessPendingMessages = false;
359 this.startingUp = false;
360 this.workerFailsDuringBootstrap = false;
361 this.publicInterface = publicInterface;
362 this.taskQueue = options.taskQueue || new ArrayTaskQueue();
363 this.runTime = hdr_histogram_js_1.build({ lowestDiscernibleValue: 1 });
364 this.waitTime = hdr_histogram_js_1.build({ lowestDiscernibleValue: 1 });
365 const filename = options.filename ? maybeFileURLToPath(options.filename) : null;
366 this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };
367 // The >= and <= could be > and < but this way we get 100 % coverage 🙃
368 if (options.maxThreads !== undefined &&
369 this.options.minThreads >= options.maxThreads) {
370 this.options.minThreads = options.maxThreads;
371 }
372 if (options.minThreads !== undefined &&
373 this.options.maxThreads <= options.minThreads) {
374 this.options.maxThreads = options.minThreads;
375 }
376 if (options.maxQueue === 'auto') {
377 this.options.maxQueue = this.options.maxThreads ** 2;
378 }
379 else {
380 this.options.maxQueue = (_a = options.maxQueue) !== null && _a !== void 0 ? _a : kDefaultOptions.maxQueue;
381 }
382 this.workers = new AsynchronouslyCreatedResourcePool(this.options.concurrentTasksPerWorker);
383 this.workers.onAvailable((w) => this._onWorkerAvailable(w));
384 this.startingUp = true;
385 this._ensureMinimumWorkers();
386 this.startingUp = false;
387 }
388 _ensureMinimumWorkers() {
389 while (this.workers.size < this.options.minThreads) {
390 this._addNewWorker();
391 }
392 }
393 _addNewWorker() {
394 const pool = this;
395 const worker = new worker_threads_1.Worker(path_1.resolve(__dirname, 'worker.js'), {
396 env: this.options.env,
397 argv: this.options.argv,
398 execArgv: this.options.execArgv,
399 resourceLimits: this.options.resourceLimits,
400 workerData: this.options.workerData,
401 trackUnmanagedFds: this.options.trackUnmanagedFds
402 });
403 const { port1, port2 } = new worker_threads_1.MessageChannel();
404 const workerInfo = new WorkerInfo(worker, port1, onMessage);
405 if (this.startingUp) {
406 // There is no point in waiting for the initial set of Workers to indicate
407 // that they are ready, we just mark them as such from the start.
408 workerInfo.markAsReady();
409 }
410 const message = {
411 filename: this.options.filename,
412 name: this.options.name,
413 port: port2,
414 sharedBuffer: workerInfo.sharedBuffer,
415 useAtomics: this.options.useAtomics,
416 niceIncrement: this.options.niceIncrement
417 };
418 worker.postMessage(message, [port2]);
419 function onMessage(message) {
420 const { taskId, result } = message;
421 // In case of success: Call the callback that was passed to `runTask`,
422 // remove the `TaskInfo` associated with the Worker, which marks it as
423 // free again.
424 const taskInfo = workerInfo.taskInfos.get(taskId);
425 workerInfo.taskInfos.delete(taskId);
426 pool.workers.maybeAvailable(workerInfo);
427 /* istanbul ignore if */
428 if (taskInfo === undefined) {
429 const err = new Error(`Unexpected message from Worker: ${util_1.inspect(message)}`);
430 pool.publicInterface.emit('error', err);
431 }
432 else {
433 taskInfo.done(message.error, result);
434 }
435 pool._processPendingMessages();
436 }
437 worker.on('message', (message) => {
438 if (message.ready === true) {
439 if (workerInfo.currentUsage() === 0) {
440 workerInfo.unref();
441 }
442 if (!workerInfo.isReady()) {
443 workerInfo.markAsReady();
444 }
445 return;
446 }
447 worker.emit('error', new Error(`Unexpected message on Worker: ${util_1.inspect(message)}`));
448 });
449 worker.on('error', (err) => {
450 // Work around the bug in https://github.com/nodejs/node/pull/33394
451 worker.ref = () => { };
452 // In case of an uncaught exception: Call the callback that was passed to
453 // `postTask` with the error, or emit an 'error' event if there is none.
454 const taskInfos = [...workerInfo.taskInfos.values()];
455 workerInfo.taskInfos.clear();
456 // Remove the worker from the list and potentially start a new Worker to
457 // replace the current one.
458 this._removeWorker(workerInfo);
459 if (workerInfo.isReady() && !this.workerFailsDuringBootstrap) {
460 this._ensureMinimumWorkers();
461 }
462 else {
463 // Do not start new workers over and over if they already fail during
464 // bootstrap, there's no point.
465 this.workerFailsDuringBootstrap = true;
466 }
467 if (taskInfos.length > 0) {
468 for (const taskInfo of taskInfos) {
469 taskInfo.done(err, null);
470 }
471 }
472 else {
473 this.publicInterface.emit('error', err);
474 }
475 });
476 worker.unref();
477 port1.on('close', () => {
478 // The port is only closed if the Worker stops for some reason, but we
479 // always .unref() the Worker itself. We want to receive e.g. 'error'
480 // events on it, so we ref it once we know it's going to exit anyway.
481 worker.ref();
482 });
483 this.workers.add(workerInfo);
484 }
485 _processPendingMessages() {
486 if (this.inProcessPendingMessages || !this.options.useAtomics) {
487 return;
488 }
489 this.inProcessPendingMessages = true;
490 try {
491 for (const workerInfo of this.workers) {
492 workerInfo.processPendingMessages();
493 }
494 }
495 finally {
496 this.inProcessPendingMessages = false;
497 }
498 }
499 _removeWorker(workerInfo) {
500 workerInfo.destroy();
501 this.workers.delete(workerInfo);
502 }
503 _onWorkerAvailable(workerInfo) {
504 while ((this.taskQueue.size > 0 || this.skipQueue.length > 0) &&
505 workerInfo.currentUsage() < this.options.concurrentTasksPerWorker) {
506 // The skipQueue will have tasks that we previously shifted off
507 // the task queue but had to skip over... we have to make sure
508 // we drain that before we drain the taskQueue.
509 const taskInfo = this.skipQueue.shift() ||
510 this.taskQueue.shift();
511 // If the task has an abortSignal and the worker has any other
512 // tasks, we cannot distribute the task to it. Skip for now.
513 if (taskInfo.abortSignal && workerInfo.taskInfos.size > 0) {
514 this.skipQueue.push(taskInfo);
515 break;
516 }
517 const now = perf_hooks_1.performance.now();
518 this.waitTime.recordValue(now - taskInfo.created);
519 taskInfo.started = now;
520 workerInfo.postTask(taskInfo);
521 this._maybeDrain();
522 return;
523 }
524 if (workerInfo.taskInfos.size === 0 &&
525 this.workers.size > this.options.minThreads) {
526 workerInfo.idleTimeout = setTimeout(() => {
527 assert_1.default.strictEqual(workerInfo.taskInfos.size, 0);
528 if (this.workers.size > this.options.minThreads) {
529 this._removeWorker(workerInfo);
530 }
531 }, this.options.idleTimeout).unref();
532 }
533 }
534 runTask(task, options) {
535 let { filename, name } = options;
536 const { transferList = [], signal = null } = options;
537 if (filename == null) {
538 filename = this.options.filename;
539 }
540 if (name == null) {
541 name = this.options.name;
542 }
543 if (typeof filename !== 'string') {
544 return Promise.reject(Errors.FilenameNotProvided());
545 }
546 filename = maybeFileURLToPath(filename);
547 let resolve;
548 let reject;
549 // eslint-disable-next-line
550 const ret = new Promise((res, rej) => { resolve = res; reject = rej; });
551 const taskInfo = new TaskInfo(task, transferList, filename, name, (err, result) => {
552 this.completed++;
553 if (taskInfo.started) {
554 this.runTime.recordValue(perf_hooks_1.performance.now() - taskInfo.started);
555 }
556 if (err !== null) {
557 reject(err);
558 }
559 else {
560 resolve(result);
561 }
562 }, signal, this.publicInterface.asyncResource.asyncId());
563 if (signal !== null) {
564 // If the AbortSignal has an aborted property and it's truthy,
565 // reject immediately.
566 if (signal.aborted) {
567 return Promise.reject(new AbortError());
568 }
569 taskInfo.abortListener = () => {
570 // Call reject() first to make sure we always reject with the AbortError
571 // if the task is aborted, not with an Error from the possible
572 // thread termination below.
573 reject(new AbortError());
574 if (taskInfo.workerInfo !== null) {
575 // Already running: We cancel the Worker this is running on.
576 this._removeWorker(taskInfo.workerInfo);
577 this._ensureMinimumWorkers();
578 }
579 else {
580 // Not yet running: Remove it from the queue.
581 this.taskQueue.remove(taskInfo);
582 }
583 };
584 onabort(signal, taskInfo.abortListener);
585 }
586 // If there is a task queue, there's no point in looking for an available
587 // Worker thread. Add this task to the queue, if possible.
588 if (this.taskQueue.size > 0) {
589 const totalCapacity = this.options.maxQueue + this.pendingCapacity();
590 if (this.taskQueue.size >= totalCapacity) {
591 if (this.options.maxQueue === 0) {
592 return Promise.reject(Errors.NoTaskQueueAvailable());
593 }
594 else {
595 return Promise.reject(Errors.TaskQueueAtLimit());
596 }
597 }
598 else {
599 if (this.workers.size < this.options.maxThreads) {
600 this._addNewWorker();
601 }
602 this.taskQueue.push(taskInfo);
603 }
604 return ret;
605 }
606 // Look for a Worker with a minimum number of tasks it is currently running.
607 let workerInfo = this.workers.findAvailable();
608 // If we want the ability to abort this task, use only workers that have
609 // no running tasks.
610 if (workerInfo !== null && workerInfo.currentUsage() > 0 && signal) {
611 workerInfo = null;
612 }
613 // If no Worker was found, or that Worker was handling another task in some
614 // way, and we still have the ability to spawn new threads, do so.
615 let waitingForNewWorker = false;
616 if ((workerInfo === null || workerInfo.currentUsage() > 0) &&
617 this.workers.size < this.options.maxThreads) {
618 this._addNewWorker();
619 waitingForNewWorker = true;
620 }
621 // If no Worker is found, try to put the task into the queue.
622 if (workerInfo === null) {
623 if (this.options.maxQueue <= 0 && !waitingForNewWorker) {
624 return Promise.reject(Errors.NoTaskQueueAvailable());
625 }
626 else {
627 this.taskQueue.push(taskInfo);
628 }
629 return ret;
630 }
631 // TODO(addaleax): Clean up the waitTime/runTime recording.
632 const now = perf_hooks_1.performance.now();
633 this.waitTime.recordValue(now - taskInfo.created);
634 taskInfo.started = now;
635 workerInfo.postTask(taskInfo);
636 this._maybeDrain();
637 return ret;
638 }
639 pendingCapacity() {
640 return this.workers.pendingItems.size *
641 this.options.concurrentTasksPerWorker;
642 }
643 _maybeDrain() {
644 if (this.taskQueue.size === 0 && this.skipQueue.length === 0) {
645 this.publicInterface.emit('drain');
646 }
647 }
648 async destroy() {
649 while (this.skipQueue.length > 0) {
650 const taskInfo = this.skipQueue.shift();
651 taskInfo.done(new Error('Terminating worker thread'));
652 }
653 while (this.taskQueue.size > 0) {
654 const taskInfo = this.taskQueue.shift();
655 taskInfo.done(new Error('Terminating worker thread'));
656 }
657 const exitEvents = [];
658 while (this.workers.size > 0) {
659 const [workerInfo] = this.workers;
660 exitEvents.push(events_1.once(workerInfo.worker, 'exit'));
661 this._removeWorker(workerInfo);
662 }
663 await Promise.all(exitEvents);
664 }
665}
666class Piscina extends eventemitter_asyncresource_1.default {
667 constructor(options = {}) {
668 super({ ...options, name: 'Piscina' });
669 _pool.set(this, void 0);
670 if (typeof options.filename !== 'string' && options.filename != null) {
671 throw new TypeError('options.filename must be a string or null');
672 }
673 if (typeof options.name !== 'string' && options.name != null) {
674 throw new TypeError('options.name must be a string or null');
675 }
676 if (options.minThreads !== undefined &&
677 (typeof options.minThreads !== 'number' || options.minThreads < 0)) {
678 throw new TypeError('options.minThreads must be a non-negative integer');
679 }
680 if (options.maxThreads !== undefined &&
681 (typeof options.maxThreads !== 'number' || options.maxThreads < 1)) {
682 throw new TypeError('options.maxThreads must be a positive integer');
683 }
684 if (options.minThreads !== undefined && options.maxThreads !== undefined &&
685 options.minThreads > options.maxThreads) {
686 throw new RangeError('options.minThreads and options.maxThreads must not conflict');
687 }
688 if (options.idleTimeout !== undefined &&
689 (typeof options.idleTimeout !== 'number' || options.idleTimeout < 0)) {
690 throw new TypeError('options.idleTimeout must be a non-negative integer');
691 }
692 if (options.maxQueue !== undefined &&
693 options.maxQueue !== 'auto' &&
694 (typeof options.maxQueue !== 'number' || options.maxQueue < 0)) {
695 throw new TypeError('options.maxQueue must be a non-negative integer');
696 }
697 if (options.concurrentTasksPerWorker !== undefined &&
698 (typeof options.concurrentTasksPerWorker !== 'number' ||
699 options.concurrentTasksPerWorker < 1)) {
700 throw new TypeError('options.concurrentTasksPerWorker must be a positive integer');
701 }
702 if (options.useAtomics !== undefined &&
703 typeof options.useAtomics !== 'boolean') {
704 throw new TypeError('options.useAtomics must be a boolean value');
705 }
706 if (options.resourceLimits !== undefined &&
707 (typeof options.resourceLimits !== 'object' ||
708 options.resourceLimits === null)) {
709 throw new TypeError('options.resourceLimits must be an object');
710 }
711 if (options.taskQueue !== undefined && !common_1.isTaskQueue(options.taskQueue)) {
712 throw new TypeError('options.taskQueue must be a TaskQueue object');
713 }
714 if (options.niceIncrement !== undefined &&
715 (typeof options.niceIncrement !== 'number' || options.niceIncrement < 0)) {
716 throw new TypeError('options.niceIncrement must be a non-negative integer');
717 }
718 if (options.trackUnmanagedFds !== undefined &&
719 typeof options.trackUnmanagedFds !== 'boolean') {
720 throw new TypeError('options.trackUnmanagedFds must be a boolean value');
721 }
722 __classPrivateFieldSet(this, _pool, new ThreadPool(this, options));
723 }
724 /** @deprecated Use run(task, options) instead **/
725 runTask(task, transferList, filename, signal) {
726 // If transferList is a string or AbortSignal, shift it.
727 if ((typeof transferList === 'object' && !Array.isArray(transferList)) ||
728 typeof transferList === 'string') {
729 signal = filename;
730 filename = transferList;
731 transferList = undefined;
732 }
733 // If filename is an AbortSignal, shift it.
734 if (typeof filename === 'object' && !Array.isArray(filename)) {
735 signal = filename;
736 filename = undefined;
737 }
738 if (transferList !== undefined && !Array.isArray(transferList)) {
739 return Promise.reject(new TypeError('transferList argument must be an Array'));
740 }
741 if (filename !== undefined && typeof filename !== 'string') {
742 return Promise.reject(new TypeError('filename argument must be a string'));
743 }
744 if (signal !== undefined && typeof signal !== 'object') {
745 return Promise.reject(new TypeError('signal argument must be an object'));
746 }
747 return __classPrivateFieldGet(this, _pool).runTask(task, {
748 transferList,
749 filename: filename || null,
750 name: 'default',
751 signal: signal || null
752 });
753 }
754 run(task, options = kDefaultRunOptions) {
755 if (options === null || typeof options !== 'object') {
756 return Promise.reject(new TypeError('options must be an object'));
757 }
758 const { transferList, filename, name, signal } = options;
759 if (transferList !== undefined && !Array.isArray(transferList)) {
760 return Promise.reject(new TypeError('transferList argument must be an Array'));
761 }
762 if (filename != null && typeof filename !== 'string') {
763 return Promise.reject(new TypeError('filename argument must be a string'));
764 }
765 if (name != null && typeof name !== 'string') {
766 return Promise.reject(new TypeError('name argument must be a string'));
767 }
768 if (signal != null && typeof signal !== 'object') {
769 return Promise.reject(new TypeError('signal argument must be an object'));
770 }
771 return __classPrivateFieldGet(this, _pool).runTask(task, { transferList, filename, name, signal });
772 }
773 destroy() {
774 return __classPrivateFieldGet(this, _pool).destroy();
775 }
776 get options() {
777 return __classPrivateFieldGet(this, _pool).options;
778 }
779 get threads() {
780 const ret = [];
781 for (const workerInfo of __classPrivateFieldGet(this, _pool).workers) {
782 ret.push(workerInfo.worker);
783 }
784 return ret;
785 }
786 get queueSize() {
787 const pool = __classPrivateFieldGet(this, _pool);
788 return Math.max(pool.taskQueue.size - pool.pendingCapacity(), 0);
789 }
790 get completed() {
791 return __classPrivateFieldGet(this, _pool).completed;
792 }
793 get waitTime() {
794 const result = hdr_histogram_percentiles_obj_1.default.histAsObj(__classPrivateFieldGet(this, _pool).waitTime);
795 return hdr_histogram_percentiles_obj_1.default.addPercentiles(__classPrivateFieldGet(this, _pool).waitTime, result);
796 }
797 get runTime() {
798 const result = hdr_histogram_percentiles_obj_1.default.histAsObj(__classPrivateFieldGet(this, _pool).runTime);
799 return hdr_histogram_percentiles_obj_1.default.addPercentiles(__classPrivateFieldGet(this, _pool).runTime, result);
800 }
801 get utilization() {
802 // The capacity is the max compute time capacity of the
803 // pool to this point in time as determined by the length
804 // of time the pool has been running multiplied by the
805 // maximum number of threads.
806 const capacity = this.duration * __classPrivateFieldGet(this, _pool).options.maxThreads;
807 const totalMeanRuntime = __classPrivateFieldGet(this, _pool).runTime.mean *
808 __classPrivateFieldGet(this, _pool).runTime.totalCount;
809 // We calculate the appoximate pool utilization by multiplying
810 // the mean run time of all tasks by the number of runtime
811 // samples taken and dividing that by the capacity. The
812 // theory here is that capacity represents the absolute upper
813 // limit of compute time this pool could ever attain (but
814 // never will for a variety of reasons. Multiplying the
815 // mean run time by the number of tasks sampled yields an
816 // approximation of the realized compute time. The utilization
817 // then becomes a point-in-time measure of how active the
818 // pool is.
819 return totalMeanRuntime / capacity;
820 }
821 get duration() {
822 return perf_hooks_1.performance.now() - __classPrivateFieldGet(this, _pool).start;
823 }
824 static get isWorkerThread() {
825 return common_1.commonState.isWorkerThread;
826 }
827 static get workerData() {
828 return common_1.commonState.workerData;
829 }
830 static get version() {
831 return package_json_1.version;
832 }
833 static get Piscina() {
834 return Piscina;
835 }
836 static move(val) {
837 if (val != null && typeof val === 'object' && typeof val !== 'function') {
838 if (!common_1.isTransferable(val)) {
839 if (util_1.types.isArrayBufferView(val)) {
840 val = new ArrayBufferViewTransferable(val);
841 }
842 else {
843 val = new DirectlyTransferable(val);
844 }
845 }
846 common_1.markMovable(val);
847 }
848 return val;
849 }
850 static get transferableSymbol() { return common_1.kTransferable; }
851 static get valueSymbol() { return common_1.kValue; }
852 static get queueOptionsSymbol() { return common_1.kQueueOptions; }
853}
854_pool = new WeakMap();
855module.exports = Piscina;
856//# sourceMappingURL=index.js.map
Note: See TracBrowser for help on using the repository browser.