index.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. "use strict";
  2. var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
  3. if (kind === "m") throw new TypeError("Private method is not writable");
  4. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
  5. if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
  6. return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
  7. };
  8. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
  9. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
  10. if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
  11. return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
  12. };
  13. var __importDefault = (this && this.__importDefault) || function (mod) {
  14. return (mod && mod.__esModule) ? mod : { "default": mod };
  15. };
  16. var _DirectlyTransferable_value, _ArrayBufferViewTransferable_view, _Piscina_pool;
  17. Object.defineProperty(exports, "__esModule", { value: true });
  18. exports.FixedQueue = exports.version = exports.queueOptionsSymbol = exports.valueSymbol = exports.transferableSymbol = exports.Piscina = exports.workerData = exports.isWorkerThread = exports.move = void 0;
  19. const node_worker_threads_1 = require("node:worker_threads");
  20. const node_events_1 = require("node:events");
  21. const node_path_1 = require("node:path");
  22. const node_util_1 = require("node:util");
  23. const node_perf_hooks_1 = require("node:perf_hooks");
  24. const promises_1 = require("node:timers/promises");
  25. const node_assert_1 = __importDefault(require("node:assert"));
  26. const package_json_1 = require("../package.json");
  27. Object.defineProperty(exports, "version", { enumerable: true, get: function () { return package_json_1.version; } });
  28. const symbols_1 = require("./symbols");
  29. Object.defineProperty(exports, "queueOptionsSymbol", { enumerable: true, get: function () { return symbols_1.kQueueOptions; } });
  30. Object.defineProperty(exports, "transferableSymbol", { enumerable: true, get: function () { return symbols_1.kTransferable; } });
  31. Object.defineProperty(exports, "valueSymbol", { enumerable: true, get: function () { return symbols_1.kValue; } });
  32. const task_queue_1 = require("./task_queue");
  33. Object.defineProperty(exports, "FixedQueue", { enumerable: true, get: function () { return task_queue_1.FixedQueue; } });
  34. const worker_pool_1 = require("./worker_pool");
  35. const abort_1 = require("./abort");
  36. const errors_1 = require("./errors");
  37. const common_1 = require("./common");
  38. const cpuParallelism = (0, common_1.getAvailableParallelism)();
  39. const kDefaultOptions = {
  40. filename: null,
  41. name: 'default',
  42. minThreads: Math.max(Math.floor(cpuParallelism / 2), 1),
  43. maxThreads: cpuParallelism * 1.5,
  44. idleTimeout: 0,
  45. maxQueue: Infinity,
  46. concurrentTasksPerWorker: 1,
  47. useAtomics: true,
  48. taskQueue: new task_queue_1.ArrayTaskQueue(),
  49. niceIncrement: 0,
  50. trackUnmanagedFds: true,
  51. closeTimeout: 30000,
  52. recordTiming: true
  53. };
  54. const kDefaultRunOptions = {
  55. transferList: undefined,
  56. filename: null,
  57. signal: null,
  58. name: null
  59. };
  60. const kDefaultCloseOptions = {
  61. force: false
  62. };
  63. class DirectlyTransferable {
  64. constructor(value) {
  65. _DirectlyTransferable_value.set(this, void 0);
  66. __classPrivateFieldSet(this, _DirectlyTransferable_value, value, "f");
  67. }
  68. get [(_DirectlyTransferable_value = new WeakMap(), symbols_1.kTransferable)]() { return __classPrivateFieldGet(this, _DirectlyTransferable_value, "f"); }
  69. get [symbols_1.kValue]() { return __classPrivateFieldGet(this, _DirectlyTransferable_value, "f"); }
  70. }
  71. class ArrayBufferViewTransferable {
  72. constructor(view) {
  73. _ArrayBufferViewTransferable_view.set(this, void 0);
  74. __classPrivateFieldSet(this, _ArrayBufferViewTransferable_view, view, "f");
  75. }
  76. get [(_ArrayBufferViewTransferable_view = new WeakMap(), symbols_1.kTransferable)]() { return __classPrivateFieldGet(this, _ArrayBufferViewTransferable_view, "f").buffer; }
  77. get [symbols_1.kValue]() { return __classPrivateFieldGet(this, _ArrayBufferViewTransferable_view, "f"); }
  78. }
  79. class ThreadPool {
  80. constructor(publicInterface, options) {
  81. var _a;
  82. this.skipQueue = [];
  83. this.completed = 0;
  84. this.start = node_perf_hooks_1.performance.now();
  85. this.inProcessPendingMessages = false;
  86. this.startingUp = false;
  87. this.closingUp = false;
  88. this.workerFailsDuringBootstrap = false;
  89. this.destroying = false;
  90. this.publicInterface = publicInterface;
  91. this.taskQueue = options.taskQueue || new task_queue_1.ArrayTaskQueue();
  92. const filename = options.filename ? (0, common_1.maybeFileURLToPath)(options.filename) : null;
  93. this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };
  94. if (this.options.recordTiming) {
  95. this.runTime = (0, node_perf_hooks_1.createHistogram)();
  96. this.waitTime = (0, node_perf_hooks_1.createHistogram)();
  97. }
  98. // The >= and <= could be > and < but this way we get 100 % coverage 🙃
  99. if (options.maxThreads !== undefined &&
  100. this.options.minThreads >= options.maxThreads) {
  101. this.options.minThreads = options.maxThreads;
  102. }
  103. if (options.minThreads !== undefined &&
  104. this.options.maxThreads <= options.minThreads) {
  105. this.options.maxThreads = options.minThreads;
  106. }
  107. if (options.maxQueue === 'auto') {
  108. this.options.maxQueue = this.options.maxThreads ** 2;
  109. }
  110. else {
  111. this.options.maxQueue = (_a = options.maxQueue) !== null && _a !== void 0 ? _a : kDefaultOptions.maxQueue;
  112. }
  113. this.workers = new worker_pool_1.AsynchronouslyCreatedResourcePool(this.options.concurrentTasksPerWorker);
  114. this.workers.onAvailable((w) => this._onWorkerAvailable(w));
  115. this.startingUp = true;
  116. this._ensureMinimumWorkers();
  117. this.startingUp = false;
  118. this.needsDrain = false;
  119. }
  120. _ensureMinimumWorkers() {
  121. if (this.closingUp || this.destroying) {
  122. return;
  123. }
  124. while (this.workers.size < this.options.minThreads) {
  125. this._addNewWorker();
  126. }
  127. }
  128. _addNewWorker() {
  129. const pool = this;
  130. const worker = new node_worker_threads_1.Worker((0, node_path_1.resolve)(__dirname, 'worker.js'), {
  131. env: this.options.env,
  132. argv: this.options.argv,
  133. execArgv: this.options.execArgv,
  134. resourceLimits: this.options.resourceLimits,
  135. workerData: this.options.workerData,
  136. trackUnmanagedFds: this.options.trackUnmanagedFds
  137. });
  138. const { port1, port2 } = new node_worker_threads_1.MessageChannel();
  139. const workerInfo = new worker_pool_1.WorkerInfo(worker, port1, onMessage);
  140. if (this.startingUp) {
  141. // There is no point in waiting for the initial set of Workers to indicate
  142. // that they are ready, we just mark them as such from the start.
  143. workerInfo.markAsReady();
  144. }
  145. const message = {
  146. filename: this.options.filename,
  147. name: this.options.name,
  148. port: port2,
  149. sharedBuffer: workerInfo.sharedBuffer,
  150. useAtomics: this.options.useAtomics,
  151. niceIncrement: this.options.niceIncrement
  152. };
  153. worker.postMessage(message, [port2]);
  154. function onMessage(message) {
  155. const { taskId, result } = message;
  156. // In case of success: Call the callback that was passed to `runTask`,
  157. // remove the `TaskInfo` associated with the Worker, which marks it as
  158. // free again.
  159. const taskInfo = workerInfo.taskInfos.get(taskId);
  160. workerInfo.taskInfos.delete(taskId);
  161. pool.workers.maybeAvailable(workerInfo);
  162. /* istanbul ignore if */
  163. if (taskInfo === undefined) {
  164. const err = new Error(`Unexpected message from Worker: ${(0, node_util_1.inspect)(message)}`);
  165. pool.publicInterface.emit('error', err);
  166. }
  167. else {
  168. taskInfo.done(message.error, result);
  169. }
  170. pool._processPendingMessages();
  171. }
  172. function onReady() {
  173. if (workerInfo.currentUsage() === 0) {
  174. workerInfo.unref();
  175. }
  176. if (!workerInfo.isReady()) {
  177. workerInfo.markAsReady();
  178. }
  179. }
  180. function onEventMessage(message) {
  181. pool.publicInterface.emit('message', message);
  182. }
  183. worker.on('message', (message) => {
  184. message instanceof Object && common_1.READY in message ? onReady() : onEventMessage(message);
  185. });
  186. worker.on('error', (err) => {
  187. this._onError(worker, workerInfo, err, false);
  188. });
  189. worker.on('exit', (exitCode) => {
  190. if (this.destroying) {
  191. return;
  192. }
  193. const err = new Error(`worker exited with code: ${exitCode}`);
  194. // Only error unfinished tasks on process exit, since there are legitimate
  195. // reasons to exit workers and we want to handle that gracefully when possible.
  196. this._onError(worker, workerInfo, err, true);
  197. });
  198. worker.unref();
  199. port1.on('close', () => {
  200. // The port is only closed if the Worker stops for some reason, but we
  201. // always .unref() the Worker itself. We want to receive e.g. 'error'
  202. // events on it, so we ref it once we know it's going to exit anyway.
  203. worker.ref();
  204. });
  205. this.workers.add(workerInfo);
  206. }
  207. _onError(worker, workerInfo, err, onlyErrorUnfinishedTasks) {
  208. // Work around the bug in https://github.com/nodejs/node/pull/33394
  209. worker.ref = () => { };
  210. const taskInfos = [...workerInfo.taskInfos.values()];
  211. workerInfo.taskInfos.clear();
  212. // Remove the worker from the list and potentially start a new Worker to
  213. // replace the current one.
  214. this._removeWorker(workerInfo);
  215. if (workerInfo.isReady() && !this.workerFailsDuringBootstrap) {
  216. this._ensureMinimumWorkers();
  217. }
  218. else {
  219. // Do not start new workers over and over if they already fail during
  220. // bootstrap, there's no point.
  221. this.workerFailsDuringBootstrap = true;
  222. }
  223. if (taskInfos.length > 0) {
  224. // If there are remaining unfinished tasks, call the callback that was
  225. // passed to `postTask` with the error
  226. for (const taskInfo of taskInfos) {
  227. taskInfo.done(err, null);
  228. }
  229. }
  230. else if (!onlyErrorUnfinishedTasks) {
  231. // If there are no unfinished tasks, instead emit an 'error' event
  232. this.publicInterface.emit('error', err);
  233. }
  234. }
  235. _processPendingMessages() {
  236. if (this.inProcessPendingMessages || !this.options.useAtomics) {
  237. return;
  238. }
  239. this.inProcessPendingMessages = true;
  240. try {
  241. for (const workerInfo of this.workers) {
  242. workerInfo.processPendingMessages();
  243. }
  244. }
  245. finally {
  246. this.inProcessPendingMessages = false;
  247. }
  248. }
  249. _removeWorker(workerInfo) {
  250. workerInfo.destroy();
  251. this.workers.delete(workerInfo);
  252. }
  253. _onWorkerAvailable(workerInfo) {
  254. var _a;
  255. while ((this.taskQueue.size > 0 || this.skipQueue.length > 0) &&
  256. workerInfo.currentUsage() < this.options.concurrentTasksPerWorker) {
  257. // The skipQueue will have tasks that we previously shifted off
  258. // the task queue but had to skip over... we have to make sure
  259. // we drain that before we drain the taskQueue.
  260. const taskInfo = this.skipQueue.shift() ||
  261. this.taskQueue.shift();
  262. // If the task has an abortSignal and the worker has any other
  263. // tasks, we cannot distribute the task to it. Skip for now.
  264. if (taskInfo.abortSignal && workerInfo.taskInfos.size > 0) {
  265. this.skipQueue.push(taskInfo);
  266. break;
  267. }
  268. const now = node_perf_hooks_1.performance.now();
  269. (_a = this.waitTime) === null || _a === void 0 ? void 0 : _a.record((0, common_1.toHistogramIntegerNano)(now - taskInfo.created));
  270. taskInfo.started = now;
  271. workerInfo.postTask(taskInfo);
  272. this._maybeDrain();
  273. return;
  274. }
  275. if (workerInfo.taskInfos.size === 0 &&
  276. this.workers.size > this.options.minThreads) {
  277. workerInfo.idleTimeout = setTimeout(() => {
  278. node_assert_1.default.strictEqual(workerInfo.taskInfos.size, 0);
  279. if (this.workers.size > this.options.minThreads) {
  280. this._removeWorker(workerInfo);
  281. }
  282. }, this.options.idleTimeout).unref();
  283. }
  284. }
  285. runTask(task, options) {
  286. var _a, _b;
  287. let { filename, name } = options;
  288. const { transferList = [] } = options;
  289. if (filename == null) {
  290. filename = this.options.filename;
  291. }
  292. if (name == null) {
  293. name = this.options.name;
  294. }
  295. if (typeof filename !== 'string') {
  296. return Promise.reject(errors_1.Errors.FilenameNotProvided());
  297. }
  298. filename = (0, common_1.maybeFileURLToPath)(filename);
  299. let signal;
  300. if (this.closingUp) {
  301. const closingUpAbortController = new AbortController();
  302. closingUpAbortController.abort('queue is closing up');
  303. signal = closingUpAbortController.signal;
  304. }
  305. else {
  306. signal = (_a = options.signal) !== null && _a !== void 0 ? _a : null;
  307. }
  308. let resolve;
  309. let reject;
  310. // eslint-disable-next-line
  311. const ret = new Promise((res, rej) => { resolve = res; reject = rej; });
  312. const taskInfo = new task_queue_1.TaskInfo(task, transferList, filename, name, (err, result) => {
  313. var _a;
  314. this.completed++;
  315. if (taskInfo.started) {
  316. (_a = this.runTime) === null || _a === void 0 ? void 0 : _a.record((0, common_1.toHistogramIntegerNano)(node_perf_hooks_1.performance.now() - taskInfo.started));
  317. }
  318. if (err !== null) {
  319. reject(err);
  320. }
  321. else {
  322. resolve(result);
  323. }
  324. this._maybeDrain();
  325. }, signal, this.publicInterface.asyncResource.asyncId());
  326. if (signal !== null) {
  327. // If the AbortSignal has an aborted property and it's truthy,
  328. // reject immediately.
  329. if (signal.aborted) {
  330. return Promise.reject(new abort_1.AbortError(signal.reason));
  331. }
  332. taskInfo.abortListener = () => {
  333. // Call reject() first to make sure we always reject with the AbortError
  334. // if the task is aborted, not with an Error from the possible
  335. // thread termination below.
  336. reject(new abort_1.AbortError(signal.reason));
  337. if (taskInfo.workerInfo !== null) {
  338. // Already running: We cancel the Worker this is running on.
  339. this._removeWorker(taskInfo.workerInfo);
  340. this._ensureMinimumWorkers();
  341. }
  342. else {
  343. // Not yet running: Remove it from the queue.
  344. this.taskQueue.remove(taskInfo);
  345. }
  346. };
  347. (0, abort_1.onabort)(signal, taskInfo.abortListener);
  348. }
  349. // If there is a task queue, there's no point in looking for an available
  350. // Worker thread. Add this task to the queue, if possible.
  351. if (this.taskQueue.size > 0) {
  352. const totalCapacity = this.options.maxQueue + this.pendingCapacity();
  353. if (this.taskQueue.size >= totalCapacity) {
  354. if (this.options.maxQueue === 0) {
  355. return Promise.reject(errors_1.Errors.NoTaskQueueAvailable());
  356. }
  357. else {
  358. return Promise.reject(errors_1.Errors.TaskQueueAtLimit());
  359. }
  360. }
  361. else {
  362. if (this.workers.size < this.options.maxThreads) {
  363. this._addNewWorker();
  364. }
  365. this.taskQueue.push(taskInfo);
  366. }
  367. this._maybeDrain();
  368. return ret;
  369. }
  370. // Look for a Worker with a minimum number of tasks it is currently running.
  371. let workerInfo = this.workers.findAvailable();
  372. // If we want the ability to abort this task, use only workers that have
  373. // no running tasks.
  374. if (workerInfo !== null && workerInfo.currentUsage() > 0 && signal) {
  375. workerInfo = null;
  376. }
  377. // If no Worker was found, or that Worker was handling another task in some
  378. // way, and we still have the ability to spawn new threads, do so.
  379. let waitingForNewWorker = false;
  380. if ((workerInfo === null || workerInfo.currentUsage() > 0) &&
  381. this.workers.size < this.options.maxThreads) {
  382. this._addNewWorker();
  383. waitingForNewWorker = true;
  384. }
  385. // If no Worker is found, try to put the task into the queue.
  386. if (workerInfo === null) {
  387. if (this.options.maxQueue <= 0 && !waitingForNewWorker) {
  388. return Promise.reject(errors_1.Errors.NoTaskQueueAvailable());
  389. }
  390. else {
  391. this.taskQueue.push(taskInfo);
  392. }
  393. this._maybeDrain();
  394. return ret;
  395. }
  396. // TODO(addaleax): Clean up the waitTime/runTime recording.
  397. const now = node_perf_hooks_1.performance.now();
  398. (_b = this.waitTime) === null || _b === void 0 ? void 0 : _b.record((0, common_1.toHistogramIntegerNano)(now - taskInfo.created));
  399. taskInfo.started = now;
  400. workerInfo.postTask(taskInfo);
  401. this._maybeDrain();
  402. return ret;
  403. }
  404. pendingCapacity() {
  405. return this.workers.pendingItems.size *
  406. this.options.concurrentTasksPerWorker;
  407. }
  408. _maybeDrain() {
  409. const totalCapacity = this.options.maxQueue + this.pendingCapacity();
  410. const totalQueueSize = this.taskQueue.size + this.skipQueue.length;
  411. if (totalQueueSize === 0) {
  412. this.needsDrain = false;
  413. this.publicInterface.emit('drain');
  414. }
  415. if (totalQueueSize >= totalCapacity) {
  416. this.needsDrain = true;
  417. this.publicInterface.emit('needsDrain');
  418. }
  419. }
  420. async destroy() {
  421. this.destroying = true;
  422. while (this.skipQueue.length > 0) {
  423. const taskInfo = this.skipQueue.shift();
  424. taskInfo.done(new Error('Terminating worker thread'));
  425. }
  426. while (this.taskQueue.size > 0) {
  427. const taskInfo = this.taskQueue.shift();
  428. taskInfo.done(new Error('Terminating worker thread'));
  429. }
  430. const exitEvents = [];
  431. while (this.workers.size > 0) {
  432. const [workerInfo] = this.workers;
  433. exitEvents.push((0, node_events_1.once)(workerInfo.worker, 'exit'));
  434. this._removeWorker(workerInfo);
  435. }
  436. try {
  437. await Promise.all(exitEvents);
  438. }
  439. finally {
  440. this.destroying = false;
  441. }
  442. }
  443. async close(options) {
  444. this.closingUp = true;
  445. if (options.force) {
  446. const skipQueueLength = this.skipQueue.length;
  447. for (let i = 0; i < skipQueueLength; i++) {
  448. const taskInfo = this.skipQueue.shift();
  449. if (taskInfo.workerInfo === null) {
  450. taskInfo.done(new abort_1.AbortError('pool is closed'));
  451. }
  452. else {
  453. this.skipQueue.push(taskInfo);
  454. }
  455. }
  456. const taskQueueLength = this.taskQueue.size;
  457. for (let i = 0; i < taskQueueLength; i++) {
  458. const taskInfo = this.taskQueue.shift();
  459. if (taskInfo.workerInfo === null) {
  460. taskInfo.done(new abort_1.AbortError('pool is closed'));
  461. }
  462. else {
  463. this.taskQueue.push(taskInfo);
  464. }
  465. }
  466. }
  467. const onPoolFlushed = () => new Promise((resolve) => {
  468. const numberOfWorkers = this.workers.size;
  469. if (numberOfWorkers === 0) {
  470. resolve();
  471. return;
  472. }
  473. let numberOfWorkersDone = 0;
  474. const checkIfWorkerIsDone = (workerInfo) => {
  475. if (workerInfo.taskInfos.size === 0) {
  476. numberOfWorkersDone++;
  477. }
  478. if (numberOfWorkers === numberOfWorkersDone) {
  479. resolve();
  480. }
  481. };
  482. for (const workerInfo of this.workers) {
  483. checkIfWorkerIsDone(workerInfo);
  484. workerInfo.port.on('message', () => checkIfWorkerIsDone(workerInfo));
  485. }
  486. });
  487. const throwOnTimeOut = async (timeout) => {
  488. await (0, promises_1.setTimeout)(timeout);
  489. throw errors_1.Errors.CloseTimeout();
  490. };
  491. try {
  492. await Promise.race([
  493. onPoolFlushed(),
  494. throwOnTimeOut(this.options.closeTimeout)
  495. ]);
  496. }
  497. catch (error) {
  498. this.publicInterface.emit('error', error);
  499. }
  500. finally {
  501. await this.destroy();
  502. this.publicInterface.emit('close');
  503. this.closingUp = false;
  504. }
  505. }
  506. }
  507. class Piscina extends node_events_1.EventEmitterAsyncResource {
  508. constructor(options = {}) {
  509. super({ ...options, name: 'Piscina' });
  510. _Piscina_pool.set(this, void 0);
  511. if (typeof options.filename !== 'string' && options.filename != null) {
  512. throw new TypeError('options.filename must be a string or null');
  513. }
  514. if (typeof options.name !== 'string' && options.name != null) {
  515. throw new TypeError('options.name must be a string or null');
  516. }
  517. if (options.minThreads !== undefined &&
  518. (typeof options.minThreads !== 'number' || options.minThreads < 0)) {
  519. throw new TypeError('options.minThreads must be a non-negative integer');
  520. }
  521. if (options.maxThreads !== undefined &&
  522. (typeof options.maxThreads !== 'number' || options.maxThreads < 1)) {
  523. throw new TypeError('options.maxThreads must be a positive integer');
  524. }
  525. if (options.minThreads !== undefined && options.maxThreads !== undefined &&
  526. options.minThreads > options.maxThreads) {
  527. throw new RangeError('options.minThreads and options.maxThreads must not conflict');
  528. }
  529. if (options.idleTimeout !== undefined &&
  530. (typeof options.idleTimeout !== 'number' || options.idleTimeout < 0)) {
  531. throw new TypeError('options.idleTimeout must be a non-negative integer');
  532. }
  533. if (options.maxQueue !== undefined &&
  534. options.maxQueue !== 'auto' &&
  535. (typeof options.maxQueue !== 'number' || options.maxQueue < 0)) {
  536. throw new TypeError('options.maxQueue must be a non-negative integer');
  537. }
  538. if (options.concurrentTasksPerWorker !== undefined &&
  539. (typeof options.concurrentTasksPerWorker !== 'number' ||
  540. options.concurrentTasksPerWorker < 1)) {
  541. throw new TypeError('options.concurrentTasksPerWorker must be a positive integer');
  542. }
  543. if (options.useAtomics !== undefined &&
  544. typeof options.useAtomics !== 'boolean') {
  545. throw new TypeError('options.useAtomics must be a boolean value');
  546. }
  547. if (options.resourceLimits !== undefined &&
  548. (typeof options.resourceLimits !== 'object' ||
  549. options.resourceLimits === null)) {
  550. throw new TypeError('options.resourceLimits must be an object');
  551. }
  552. if (options.taskQueue !== undefined && !(0, task_queue_1.isTaskQueue)(options.taskQueue)) {
  553. throw new TypeError('options.taskQueue must be a TaskQueue object');
  554. }
  555. if (options.niceIncrement !== undefined &&
  556. (typeof options.niceIncrement !== 'number' || (options.niceIncrement < 0 && process.platform !== 'win32'))) {
  557. throw new TypeError('options.niceIncrement must be a non-negative integer on Unix systems');
  558. }
  559. if (options.trackUnmanagedFds !== undefined &&
  560. typeof options.trackUnmanagedFds !== 'boolean') {
  561. throw new TypeError('options.trackUnmanagedFds must be a boolean value');
  562. }
  563. if (options.closeTimeout !== undefined && (typeof options.closeTimeout !== 'number' || options.closeTimeout < 0)) {
  564. throw new TypeError('options.closeTimeout must be a non-negative integer');
  565. }
  566. __classPrivateFieldSet(this, _Piscina_pool, new ThreadPool(this, options), "f");
  567. }
  568. /** @deprecated Use run(task, options) instead **/
  569. runTask(task, transferList, filename, signal) {
  570. // If transferList is a string or AbortSignal, shift it.
  571. if ((typeof transferList === 'object' && !Array.isArray(transferList)) ||
  572. typeof transferList === 'string') {
  573. signal = filename;
  574. filename = transferList;
  575. transferList = undefined;
  576. }
  577. // If filename is an AbortSignal, shift it.
  578. if (typeof filename === 'object' && !Array.isArray(filename)) {
  579. signal = filename;
  580. filename = undefined;
  581. }
  582. if (transferList !== undefined && !Array.isArray(transferList)) {
  583. return Promise.reject(new TypeError('transferList argument must be an Array'));
  584. }
  585. if (filename !== undefined && typeof filename !== 'string') {
  586. return Promise.reject(new TypeError('filename argument must be a string'));
  587. }
  588. if (signal !== undefined && typeof signal !== 'object') {
  589. return Promise.reject(new TypeError('signal argument must be an object'));
  590. }
  591. return __classPrivateFieldGet(this, _Piscina_pool, "f").runTask(task, {
  592. transferList,
  593. filename: filename || null,
  594. name: 'default',
  595. signal: signal || null
  596. });
  597. }
  598. run(task, options = kDefaultRunOptions) {
  599. if (options === null || typeof options !== 'object') {
  600. return Promise.reject(new TypeError('options must be an object'));
  601. }
  602. const { transferList, filename, name, signal } = options;
  603. if (transferList !== undefined && !Array.isArray(transferList)) {
  604. return Promise.reject(new TypeError('transferList argument must be an Array'));
  605. }
  606. if (filename != null && typeof filename !== 'string') {
  607. return Promise.reject(new TypeError('filename argument must be a string'));
  608. }
  609. if (name != null && typeof name !== 'string') {
  610. return Promise.reject(new TypeError('name argument must be a string'));
  611. }
  612. if (signal != null && typeof signal !== 'object') {
  613. return Promise.reject(new TypeError('signal argument must be an object'));
  614. }
  615. return __classPrivateFieldGet(this, _Piscina_pool, "f").runTask(task, { transferList, filename, name, signal });
  616. }
  617. async close(options = kDefaultCloseOptions) {
  618. if (options === null || typeof options !== 'object') {
  619. throw TypeError('options must be an object');
  620. }
  621. let { force } = options;
  622. if (force !== undefined && typeof force !== 'boolean') {
  623. return Promise.reject(new TypeError('force argument must be a boolean'));
  624. }
  625. force !== null && force !== void 0 ? force : (force = kDefaultCloseOptions.force);
  626. return __classPrivateFieldGet(this, _Piscina_pool, "f").close({
  627. force
  628. });
  629. }
  630. destroy() {
  631. return __classPrivateFieldGet(this, _Piscina_pool, "f").destroy();
  632. }
  633. get maxThreads() {
  634. return __classPrivateFieldGet(this, _Piscina_pool, "f").options.maxThreads;
  635. }
  636. get minThreads() {
  637. return __classPrivateFieldGet(this, _Piscina_pool, "f").options.minThreads;
  638. }
  639. get options() {
  640. return __classPrivateFieldGet(this, _Piscina_pool, "f").options;
  641. }
  642. get threads() {
  643. const ret = [];
  644. for (const workerInfo of __classPrivateFieldGet(this, _Piscina_pool, "f").workers) {
  645. ret.push(workerInfo.worker);
  646. }
  647. return ret;
  648. }
  649. get queueSize() {
  650. const pool = __classPrivateFieldGet(this, _Piscina_pool, "f");
  651. return Math.max(pool.taskQueue.size - pool.pendingCapacity(), 0);
  652. }
  653. get completed() {
  654. return __classPrivateFieldGet(this, _Piscina_pool, "f").completed;
  655. }
  656. get waitTime() {
  657. if (!__classPrivateFieldGet(this, _Piscina_pool, "f").waitTime) {
  658. return null;
  659. }
  660. return (0, common_1.createHistogramSummary)(__classPrivateFieldGet(this, _Piscina_pool, "f").waitTime);
  661. }
  662. get runTime() {
  663. if (!__classPrivateFieldGet(this, _Piscina_pool, "f").runTime) {
  664. return null;
  665. }
  666. return (0, common_1.createHistogramSummary)(__classPrivateFieldGet(this, _Piscina_pool, "f").runTime);
  667. }
  668. get utilization() {
  669. if (!__classPrivateFieldGet(this, _Piscina_pool, "f").runTime) {
  670. return 0;
  671. }
  672. // count is available as of Node.js v16.14.0 but not present in the types
  673. const count = __classPrivateFieldGet(this, _Piscina_pool, "f").runTime.count;
  674. if (count === 0) {
  675. return 0;
  676. }
  677. // The capacity is the max compute time capacity of the
  678. // pool to this point in time as determined by the length
  679. // of time the pool has been running multiplied by the
  680. // maximum number of threads.
  681. const capacity = this.duration * __classPrivateFieldGet(this, _Piscina_pool, "f").options.maxThreads;
  682. const totalMeanRuntime = (__classPrivateFieldGet(this, _Piscina_pool, "f").runTime.mean / 1000) * count;
  683. // We calculate the appoximate pool utilization by multiplying
  684. // the mean run time of all tasks by the number of runtime
  685. // samples taken and dividing that by the capacity. The
  686. // theory here is that capacity represents the absolute upper
  687. // limit of compute time this pool could ever attain (but
  688. // never will for a variety of reasons. Multiplying the
  689. // mean run time by the number of tasks sampled yields an
  690. // approximation of the realized compute time. The utilization
  691. // then becomes a point-in-time measure of how active the
  692. // pool is.
  693. return totalMeanRuntime / capacity;
  694. }
  695. get duration() {
  696. return node_perf_hooks_1.performance.now() - __classPrivateFieldGet(this, _Piscina_pool, "f").start;
  697. }
  698. get needsDrain() {
  699. return __classPrivateFieldGet(this, _Piscina_pool, "f").needsDrain;
  700. }
  701. static get isWorkerThread() {
  702. return common_1.commonState.isWorkerThread;
  703. }
  704. static get workerData() {
  705. return common_1.commonState.workerData;
  706. }
  707. static get version() {
  708. return package_json_1.version;
  709. }
  710. static get Piscina() {
  711. return Piscina;
  712. }
  713. static get FixedQueue() {
  714. return task_queue_1.FixedQueue;
  715. }
  716. static get ArrayTaskQueue() {
  717. return task_queue_1.ArrayTaskQueue;
  718. }
  719. static move(val) {
  720. if (val != null && typeof val === 'object' && typeof val !== 'function') {
  721. if (!(0, common_1.isTransferable)(val)) {
  722. if (node_util_1.types.isArrayBufferView(val)) {
  723. val = new ArrayBufferViewTransferable(val);
  724. }
  725. else {
  726. val = new DirectlyTransferable(val);
  727. }
  728. }
  729. (0, common_1.markMovable)(val);
  730. }
  731. return val;
  732. }
  733. static get transferableSymbol() { return symbols_1.kTransferable; }
  734. static get valueSymbol() { return symbols_1.kValue; }
  735. static get queueOptionsSymbol() { return symbols_1.kQueueOptions; }
  736. }
  737. exports.Piscina = Piscina;
  738. _Piscina_pool = new WeakMap();
  739. exports.default = Piscina;
  740. exports.move = Piscina.move;
  741. exports.isWorkerThread = Piscina.isWorkerThread;
  742. exports.workerData = Piscina.workerData;
  743. //# sourceMappingURL=index.js.map