You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
819 lines
24 KiB
819 lines
24 KiB
2 years ago
|
"use strict";
|
||
|
import {
|
||
|
__dirname,
|
||
|
__privateAdd,
|
||
|
__privateGet,
|
||
|
__privateSet,
|
||
|
__publicField,
|
||
|
isMovable,
|
||
|
isTaskQueue,
|
||
|
isTransferable,
|
||
|
kFieldCount,
|
||
|
kQueueOptions,
|
||
|
kRequestCountField,
|
||
|
kResponseCountField,
|
||
|
kTransferable,
|
||
|
kValue,
|
||
|
markMovable
|
||
|
} from "./chunk-QYFJIXNO.js";
|
||
|
|
||
|
// src/index.ts
|
||
|
import {
|
||
|
Worker,
|
||
|
MessageChannel,
|
||
|
receiveMessageOnPort
|
||
|
} from "worker_threads";
|
||
|
import { once } from "events";
|
||
|
|
||
|
// src/EventEmitterAsyncResource.ts
|
||
|
import { EventEmitter } from "events";
|
||
|
import { AsyncResource } from "async_hooks";
|
||
|
var kEventEmitter = Symbol("kEventEmitter");
|
||
|
var kAsyncResource = Symbol("kAsyncResource");
|
||
|
var _a;
|
||
|
var EventEmitterReferencingAsyncResource = class extends AsyncResource {
|
||
|
constructor(ee, type, options) {
|
||
|
super(type, options);
|
||
|
__publicField(this, _a);
|
||
|
this[kEventEmitter] = ee;
|
||
|
}
|
||
|
get eventEmitter() {
|
||
|
return this[kEventEmitter];
|
||
|
}
|
||
|
};
|
||
|
_a = kEventEmitter;
|
||
|
var _a2;
|
||
|
var _EventEmitterAsyncResource = class extends EventEmitter {
|
||
|
constructor(options) {
|
||
|
let name;
|
||
|
if (typeof options === "string") {
|
||
|
name = options;
|
||
|
options = void 0;
|
||
|
} else {
|
||
|
name = options?.name || new.target.name;
|
||
|
}
|
||
|
super(options);
|
||
|
__publicField(this, _a2);
|
||
|
this[kAsyncResource] = new EventEmitterReferencingAsyncResource(this, name, options);
|
||
|
}
|
||
|
emit(event, ...args) {
|
||
|
return this.asyncResource.runInAsyncScope(super.emit, this, event, ...args);
|
||
|
}
|
||
|
emitDestroy() {
|
||
|
this.asyncResource.emitDestroy();
|
||
|
}
|
||
|
asyncId() {
|
||
|
return this.asyncResource.asyncId();
|
||
|
}
|
||
|
triggerAsyncId() {
|
||
|
return this.asyncResource.triggerAsyncId();
|
||
|
}
|
||
|
get asyncResource() {
|
||
|
return this[kAsyncResource];
|
||
|
}
|
||
|
static get EventEmitterAsyncResource() {
|
||
|
return _EventEmitterAsyncResource;
|
||
|
}
|
||
|
};
|
||
|
var EventEmitterAsyncResource = _EventEmitterAsyncResource;
|
||
|
_a2 = kAsyncResource;
|
||
|
var EventEmitterAsyncResource_default = EventEmitterAsyncResource;
|
||
|
|
||
|
// src/index.ts
|
||
|
import { AsyncResource as AsyncResource2 } from "async_hooks";
|
||
|
import { fileURLToPath, URL } from "url";
|
||
|
import { dirname, join, resolve } from "path";
|
||
|
import { inspect, types } from "util";
|
||
|
import assert from "assert";
|
||
|
import { performance } from "perf_hooks";
|
||
|
import { readFileSync } from "fs";
|
||
|
|
||
|
// src/physicalCpuCount.ts
|
||
|
import os from "os";
|
||
|
import childProcess from "child_process";
|
||
|
function exec(command) {
|
||
|
const output = childProcess.execSync(command, {
|
||
|
encoding: "utf8",
|
||
|
stdio: [null, null, null]
|
||
|
});
|
||
|
return output;
|
||
|
}
|
||
|
var amount;
|
||
|
try {
|
||
|
const platform = os.platform();
|
||
|
if (platform === "linux") {
|
||
|
const output1 = exec('cat /proc/cpuinfo | grep "physical id" | sort |uniq | wc -l');
|
||
|
const output2 = exec('cat /proc/cpuinfo | grep "core id" | sort | uniq | wc -l');
|
||
|
const physicalCpuAmount = parseInt(output1.trim(), 10);
|
||
|
const physicalCoreAmount = parseInt(output2.trim(), 10);
|
||
|
amount = physicalCpuAmount * physicalCoreAmount;
|
||
|
} else if (platform === "darwin") {
|
||
|
const output = exec("sysctl -n hw.physicalcpu_max");
|
||
|
amount = parseInt(output.trim(), 10);
|
||
|
} else if (platform === "win32") {
|
||
|
throw new Error();
|
||
|
} else {
|
||
|
const cores = os.cpus().filter(function(cpu, index) {
|
||
|
const hasHyperthreading = cpu.model.includes("Intel");
|
||
|
const isOdd = index % 2 === 1;
|
||
|
return !hasHyperthreading || isOdd;
|
||
|
});
|
||
|
amount = cores.length;
|
||
|
}
|
||
|
} catch {
|
||
|
amount = os.cpus().length;
|
||
|
}
|
||
|
|
||
|
// src/index.ts
|
||
|
var cpuCount = amount;
|
||
|
function onabort(abortSignal, listener) {
|
||
|
if ("addEventListener" in abortSignal) {
|
||
|
abortSignal.addEventListener("abort", listener, { once: true });
|
||
|
} else {
|
||
|
abortSignal.once("abort", listener);
|
||
|
}
|
||
|
}
|
||
|
var AbortError = class extends Error {
|
||
|
constructor() {
|
||
|
super("The task has been aborted");
|
||
|
}
|
||
|
get name() {
|
||
|
return "AbortError";
|
||
|
}
|
||
|
};
|
||
|
var ArrayTaskQueue = class {
|
||
|
constructor() {
|
||
|
__publicField(this, "tasks", []);
|
||
|
}
|
||
|
get size() {
|
||
|
return this.tasks.length;
|
||
|
}
|
||
|
shift() {
|
||
|
return this.tasks.shift();
|
||
|
}
|
||
|
push(task) {
|
||
|
this.tasks.push(task);
|
||
|
}
|
||
|
remove(task) {
|
||
|
const index = this.tasks.indexOf(task);
|
||
|
assert.notStrictEqual(index, -1);
|
||
|
this.tasks.splice(index, 1);
|
||
|
}
|
||
|
};
|
||
|
var kDefaultOptions = {
|
||
|
filename: null,
|
||
|
name: "default",
|
||
|
minThreads: Math.max(cpuCount / 2, 1),
|
||
|
maxThreads: cpuCount,
|
||
|
idleTimeout: 0,
|
||
|
maxQueue: Infinity,
|
||
|
concurrentTasksPerWorker: 1,
|
||
|
useAtomics: true,
|
||
|
taskQueue: new ArrayTaskQueue(),
|
||
|
trackUnmanagedFds: true
|
||
|
};
|
||
|
var kDefaultRunOptions = {
|
||
|
transferList: void 0,
|
||
|
filename: null,
|
||
|
signal: null,
|
||
|
name: null
|
||
|
};
|
||
|
var _value;
|
||
|
var DirectlyTransferable = class {
|
||
|
constructor(value) {
|
||
|
__privateAdd(this, _value, void 0);
|
||
|
__privateSet(this, _value, value);
|
||
|
}
|
||
|
get [kTransferable]() {
|
||
|
return __privateGet(this, _value);
|
||
|
}
|
||
|
get [kValue]() {
|
||
|
return __privateGet(this, _value);
|
||
|
}
|
||
|
};
|
||
|
_value = new WeakMap();
|
||
|
var _view;
|
||
|
var ArrayBufferViewTransferable = class {
|
||
|
constructor(view) {
|
||
|
__privateAdd(this, _view, void 0);
|
||
|
__privateSet(this, _view, view);
|
||
|
}
|
||
|
get [kTransferable]() {
|
||
|
return __privateGet(this, _view).buffer;
|
||
|
}
|
||
|
get [kValue]() {
|
||
|
return __privateGet(this, _view);
|
||
|
}
|
||
|
};
|
||
|
_view = new WeakMap();
|
||
|
var taskIdCounter = 0;
|
||
|
function maybeFileURLToPath(filename) {
|
||
|
return filename.startsWith("file:") ? fileURLToPath(new URL(filename)) : filename;
|
||
|
}
|
||
|
var TaskInfo = class extends AsyncResource2 {
|
||
|
constructor(task, transferList, filename, name, callback, abortSignal, triggerAsyncId) {
|
||
|
super("Tinypool.Task", { requireManualDestroy: true, triggerAsyncId });
|
||
|
__publicField(this, "callback");
|
||
|
__publicField(this, "task");
|
||
|
__publicField(this, "transferList");
|
||
|
__publicField(this, "filename");
|
||
|
__publicField(this, "name");
|
||
|
__publicField(this, "taskId");
|
||
|
__publicField(this, "abortSignal");
|
||
|
__publicField(this, "abortListener", null);
|
||
|
__publicField(this, "workerInfo", null);
|
||
|
__publicField(this, "created");
|
||
|
__publicField(this, "started");
|
||
|
this.callback = callback;
|
||
|
this.task = task;
|
||
|
this.transferList = transferList;
|
||
|
if (isMovable(task)) {
|
||
|
if (this.transferList == null) {
|
||
|
this.transferList = [];
|
||
|
}
|
||
|
this.transferList = this.transferList.concat(task[kTransferable]);
|
||
|
this.task = task[kValue];
|
||
|
}
|
||
|
this.filename = filename;
|
||
|
this.name = name;
|
||
|
this.taskId = taskIdCounter++;
|
||
|
this.abortSignal = abortSignal;
|
||
|
this.created = performance.now();
|
||
|
this.started = 0;
|
||
|
}
|
||
|
releaseTask() {
|
||
|
const ret = this.task;
|
||
|
this.task = null;
|
||
|
return ret;
|
||
|
}
|
||
|
done(err, result) {
|
||
|
this.emitDestroy();
|
||
|
this.runInAsyncScope(this.callback, null, err, result);
|
||
|
if (this.abortSignal && this.abortListener) {
|
||
|
if ("removeEventListener" in this.abortSignal && this.abortListener) {
|
||
|
this.abortSignal.removeEventListener("abort", this.abortListener);
|
||
|
} else {
|
||
|
;
|
||
|
this.abortSignal.off("abort", this.abortListener);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
get [kQueueOptions]() {
|
||
|
return kQueueOptions in this.task ? this.task[kQueueOptions] : null;
|
||
|
}
|
||
|
};
|
||
|
var AsynchronouslyCreatedResource = class {
|
||
|
constructor() {
|
||
|
__publicField(this, "onreadyListeners", []);
|
||
|
}
|
||
|
markAsReady() {
|
||
|
const listeners = this.onreadyListeners;
|
||
|
assert(listeners !== null);
|
||
|
this.onreadyListeners = null;
|
||
|
for (const listener of listeners) {
|
||
|
listener();
|
||
|
}
|
||
|
}
|
||
|
isReady() {
|
||
|
return this.onreadyListeners === null;
|
||
|
}
|
||
|
onReady(fn) {
|
||
|
if (this.onreadyListeners === null) {
|
||
|
fn();
|
||
|
return;
|
||
|
}
|
||
|
this.onreadyListeners.push(fn);
|
||
|
}
|
||
|
};
|
||
|
var AsynchronouslyCreatedResourcePool = class {
|
||
|
constructor(maximumUsage) {
|
||
|
__publicField(this, "pendingItems", /* @__PURE__ */ new Set());
|
||
|
__publicField(this, "readyItems", /* @__PURE__ */ new Set());
|
||
|
__publicField(this, "maximumUsage");
|
||
|
__publicField(this, "onAvailableListeners");
|
||
|
this.maximumUsage = maximumUsage;
|
||
|
this.onAvailableListeners = [];
|
||
|
}
|
||
|
add(item) {
|
||
|
this.pendingItems.add(item);
|
||
|
item.onReady(() => {
|
||
|
if (this.pendingItems.has(item)) {
|
||
|
this.pendingItems.delete(item);
|
||
|
this.readyItems.add(item);
|
||
|
this.maybeAvailable(item);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
delete(item) {
|
||
|
this.pendingItems.delete(item);
|
||
|
this.readyItems.delete(item);
|
||
|
}
|
||
|
findAvailable() {
|
||
|
let minUsage = this.maximumUsage;
|
||
|
let candidate = null;
|
||
|
for (const item of this.readyItems) {
|
||
|
const usage = item.currentUsage();
|
||
|
if (usage === 0)
|
||
|
return item;
|
||
|
if (usage < minUsage) {
|
||
|
candidate = item;
|
||
|
minUsage = usage;
|
||
|
}
|
||
|
}
|
||
|
return candidate;
|
||
|
}
|
||
|
*[Symbol.iterator]() {
|
||
|
yield* this.pendingItems;
|
||
|
yield* this.readyItems;
|
||
|
}
|
||
|
get size() {
|
||
|
return this.pendingItems.size + this.readyItems.size;
|
||
|
}
|
||
|
maybeAvailable(item) {
|
||
|
if (item.currentUsage() < this.maximumUsage) {
|
||
|
for (const listener of this.onAvailableListeners) {
|
||
|
listener(item);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
onAvailable(fn) {
|
||
|
this.onAvailableListeners.push(fn);
|
||
|
}
|
||
|
};
|
||
|
var Errors = {
|
||
|
ThreadTermination: () => new Error("Terminating worker thread"),
|
||
|
FilenameNotProvided: () => new Error("filename must be provided to run() or in options object"),
|
||
|
TaskQueueAtLimit: () => new Error("Task queue is at limit"),
|
||
|
NoTaskQueueAvailable: () => new Error("No task queue available and all Workers are busy")
|
||
|
};
|
||
|
var WorkerInfo = class extends AsynchronouslyCreatedResource {
|
||
|
constructor(worker, port, workerId, freeWorkerId, onMessage) {
|
||
|
super();
|
||
|
__publicField(this, "worker");
|
||
|
__publicField(this, "workerId");
|
||
|
__publicField(this, "freeWorkerId");
|
||
|
__publicField(this, "taskInfos");
|
||
|
__publicField(this, "idleTimeout", null);
|
||
|
__publicField(this, "port");
|
||
|
__publicField(this, "sharedBuffer");
|
||
|
__publicField(this, "lastSeenResponseCount", 0);
|
||
|
__publicField(this, "onMessage");
|
||
|
this.worker = worker;
|
||
|
this.workerId = workerId;
|
||
|
this.freeWorkerId = freeWorkerId;
|
||
|
this.port = port;
|
||
|
this.port.on("message", (message) => this._handleResponse(message));
|
||
|
this.onMessage = onMessage;
|
||
|
this.taskInfos = /* @__PURE__ */ new Map();
|
||
|
this.sharedBuffer = new Int32Array(new SharedArrayBuffer(kFieldCount * Int32Array.BYTES_PER_ELEMENT));
|
||
|
}
|
||
|
destroy() {
|
||
|
this.worker.terminate();
|
||
|
this.port.close();
|
||
|
this.freeWorkerId();
|
||
|
this.clearIdleTimeout();
|
||
|
for (const taskInfo of this.taskInfos.values()) {
|
||
|
taskInfo.done(Errors.ThreadTermination());
|
||
|
}
|
||
|
this.taskInfos.clear();
|
||
|
}
|
||
|
clearIdleTimeout() {
|
||
|
if (this.idleTimeout !== null) {
|
||
|
clearTimeout(this.idleTimeout);
|
||
|
this.idleTimeout = null;
|
||
|
}
|
||
|
}
|
||
|
ref() {
|
||
|
this.port.ref();
|
||
|
return this;
|
||
|
}
|
||
|
unref() {
|
||
|
this.port.unref();
|
||
|
return this;
|
||
|
}
|
||
|
_handleResponse(message) {
|
||
|
this.onMessage(message);
|
||
|
if (this.taskInfos.size === 0) {
|
||
|
this.unref();
|
||
|
}
|
||
|
}
|
||
|
postTask(taskInfo) {
|
||
|
assert(!this.taskInfos.has(taskInfo.taskId));
|
||
|
const message = {
|
||
|
task: taskInfo.releaseTask(),
|
||
|
taskId: taskInfo.taskId,
|
||
|
filename: taskInfo.filename,
|
||
|
name: taskInfo.name
|
||
|
};
|
||
|
try {
|
||
|
this.port.postMessage(message, taskInfo.transferList);
|
||
|
} catch (err) {
|
||
|
taskInfo.done(err);
|
||
|
return;
|
||
|
}
|
||
|
taskInfo.workerInfo = this;
|
||
|
this.taskInfos.set(taskInfo.taskId, taskInfo);
|
||
|
this.ref();
|
||
|
this.clearIdleTimeout();
|
||
|
Atomics.add(this.sharedBuffer, kRequestCountField, 1);
|
||
|
Atomics.notify(this.sharedBuffer, kRequestCountField, 1);
|
||
|
}
|
||
|
processPendingMessages() {
|
||
|
const actualResponseCount = Atomics.load(this.sharedBuffer, kResponseCountField);
|
||
|
if (actualResponseCount !== this.lastSeenResponseCount) {
|
||
|
this.lastSeenResponseCount = actualResponseCount;
|
||
|
let entry;
|
||
|
while ((entry = receiveMessageOnPort(this.port)) !== void 0) {
|
||
|
this._handleResponse(entry.message);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
isRunningAbortableTask() {
|
||
|
if (this.taskInfos.size !== 1)
|
||
|
return false;
|
||
|
const [[, task]] = this.taskInfos;
|
||
|
return task.abortSignal !== null;
|
||
|
}
|
||
|
currentUsage() {
|
||
|
if (this.isRunningAbortableTask())
|
||
|
return Infinity;
|
||
|
return this.taskInfos.size;
|
||
|
}
|
||
|
};
|
||
|
var ThreadPool = class {
|
||
|
constructor(publicInterface, options) {
|
||
|
__publicField(this, "publicInterface");
|
||
|
__publicField(this, "workers");
|
||
|
__publicField(this, "workerIds");
|
||
|
__publicField(this, "options");
|
||
|
__publicField(this, "taskQueue");
|
||
|
__publicField(this, "skipQueue", []);
|
||
|
__publicField(this, "completed", 0);
|
||
|
__publicField(this, "start", performance.now());
|
||
|
__publicField(this, "inProcessPendingMessages", false);
|
||
|
__publicField(this, "startingUp", false);
|
||
|
__publicField(this, "workerFailsDuringBootstrap", false);
|
||
|
this.publicInterface = publicInterface;
|
||
|
this.taskQueue = options.taskQueue || new ArrayTaskQueue();
|
||
|
const filename = options.filename ? maybeFileURLToPath(options.filename) : null;
|
||
|
this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };
|
||
|
if (options.maxThreads !== void 0 && this.options.minThreads >= options.maxThreads) {
|
||
|
this.options.minThreads = options.maxThreads;
|
||
|
}
|
||
|
if (options.minThreads !== void 0 && this.options.maxThreads <= options.minThreads) {
|
||
|
this.options.maxThreads = options.minThreads;
|
||
|
}
|
||
|
if (options.maxQueue === "auto") {
|
||
|
this.options.maxQueue = this.options.maxThreads ** 2;
|
||
|
} else {
|
||
|
this.options.maxQueue = options.maxQueue ?? kDefaultOptions.maxQueue;
|
||
|
}
|
||
|
this.workerIds = new Map(new Array(options.maxThreads).fill(0).map((_, i) => [i + 1, true]));
|
||
|
this.workers = new AsynchronouslyCreatedResourcePool(this.options.concurrentTasksPerWorker);
|
||
|
this.workers.onAvailable((w) => this._onWorkerAvailable(w));
|
||
|
this.startingUp = true;
|
||
|
this._ensureMinimumWorkers();
|
||
|
this.startingUp = false;
|
||
|
}
|
||
|
_ensureMaximumWorkers() {
|
||
|
while (this.workers.size < this.options.maxThreads) {
|
||
|
this._addNewWorker();
|
||
|
}
|
||
|
}
|
||
|
_ensureMinimumWorkers() {
|
||
|
while (this.workers.size < this.options.minThreads) {
|
||
|
this._addNewWorker();
|
||
|
}
|
||
|
}
|
||
|
_addNewWorker() {
|
||
|
const pool = this;
|
||
|
const workerIds = this.workerIds;
|
||
|
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
||
|
let workerId;
|
||
|
workerIds.forEach((isIdAvailable, _workerId2) => {
|
||
|
if (isIdAvailable && !workerId) {
|
||
|
workerId = _workerId2;
|
||
|
workerIds.set(_workerId2, false);
|
||
|
}
|
||
|
});
|
||
|
const tinypoolPrivateData = { workerId };
|
||
|
const worker = new Worker(resolve(__dirname2, "./worker.js"), {
|
||
|
env: this.options.env,
|
||
|
argv: this.options.argv,
|
||
|
execArgv: this.options.execArgv,
|
||
|
resourceLimits: this.options.resourceLimits,
|
||
|
workerData: [
|
||
|
tinypoolPrivateData,
|
||
|
this.options.workerData
|
||
|
],
|
||
|
trackUnmanagedFds: this.options.trackUnmanagedFds
|
||
|
});
|
||
|
const onMessage = (message2) => {
|
||
|
const { taskId, result } = message2;
|
||
|
const taskInfo = workerInfo.taskInfos.get(taskId);
|
||
|
workerInfo.taskInfos.delete(taskId);
|
||
|
if (!this.options.isolateWorkers)
|
||
|
pool.workers.maybeAvailable(workerInfo);
|
||
|
if (taskInfo === void 0) {
|
||
|
const err = new Error(`Unexpected message from Worker: ${inspect(message2)}`);
|
||
|
pool.publicInterface.emit("error", err);
|
||
|
} else {
|
||
|
taskInfo.done(message2.error, result);
|
||
|
}
|
||
|
pool._processPendingMessages();
|
||
|
};
|
||
|
const { port1, port2 } = new MessageChannel();
|
||
|
const workerInfo = new WorkerInfo(worker, port1, workerId, () => workerIds.set(workerId, true), onMessage);
|
||
|
if (this.startingUp) {
|
||
|
workerInfo.markAsReady();
|
||
|
}
|
||
|
const message = {
|
||
|
filename: this.options.filename,
|
||
|
name: this.options.name,
|
||
|
port: port2,
|
||
|
sharedBuffer: workerInfo.sharedBuffer,
|
||
|
useAtomics: this.options.useAtomics
|
||
|
};
|
||
|
worker.postMessage(message, [port2]);
|
||
|
worker.on("message", (message2) => {
|
||
|
if (message2.ready === true) {
|
||
|
if (workerInfo.currentUsage() === 0) {
|
||
|
workerInfo.unref();
|
||
|
}
|
||
|
if (!workerInfo.isReady()) {
|
||
|
workerInfo.markAsReady();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
worker.emit("error", new Error(`Unexpected message on Worker: ${inspect(message2)}`));
|
||
|
});
|
||
|
worker.on("error", (err) => {
|
||
|
worker.ref = () => {
|
||
|
};
|
||
|
const taskInfos = [...workerInfo.taskInfos.values()];
|
||
|
workerInfo.taskInfos.clear();
|
||
|
this._removeWorker(workerInfo);
|
||
|
if (workerInfo.isReady() && !this.workerFailsDuringBootstrap) {
|
||
|
this._ensureMinimumWorkers();
|
||
|
} else {
|
||
|
this.workerFailsDuringBootstrap = true;
|
||
|
}
|
||
|
if (taskInfos.length > 0) {
|
||
|
for (const taskInfo of taskInfos) {
|
||
|
taskInfo.done(err, null);
|
||
|
}
|
||
|
} else {
|
||
|
this.publicInterface.emit("error", err);
|
||
|
}
|
||
|
});
|
||
|
worker.unref();
|
||
|
port1.on("close", () => {
|
||
|
worker.ref();
|
||
|
});
|
||
|
this.workers.add(workerInfo);
|
||
|
}
|
||
|
_processPendingMessages() {
|
||
|
if (this.inProcessPendingMessages || !this.options.useAtomics) {
|
||
|
return;
|
||
|
}
|
||
|
this.inProcessPendingMessages = true;
|
||
|
try {
|
||
|
for (const workerInfo of this.workers) {
|
||
|
workerInfo.processPendingMessages();
|
||
|
}
|
||
|
} finally {
|
||
|
this.inProcessPendingMessages = false;
|
||
|
}
|
||
|
}
|
||
|
_removeWorker(workerInfo) {
|
||
|
workerInfo.destroy();
|
||
|
this.workers.delete(workerInfo);
|
||
|
}
|
||
|
_onWorkerAvailable(workerInfo) {
|
||
|
while ((this.taskQueue.size > 0 || this.skipQueue.length > 0) && workerInfo.currentUsage() < this.options.concurrentTasksPerWorker) {
|
||
|
const taskInfo = this.skipQueue.shift() || this.taskQueue.shift();
|
||
|
if (taskInfo.abortSignal && workerInfo.taskInfos.size > 0) {
|
||
|
this.skipQueue.push(taskInfo);
|
||
|
break;
|
||
|
}
|
||
|
const now = performance.now();
|
||
|
taskInfo.started = now;
|
||
|
workerInfo.postTask(taskInfo);
|
||
|
this._maybeDrain();
|
||
|
return;
|
||
|
}
|
||
|
if (workerInfo.taskInfos.size === 0 && this.workers.size > this.options.minThreads) {
|
||
|
workerInfo.idleTimeout = setTimeout(() => {
|
||
|
assert.strictEqual(workerInfo.taskInfos.size, 0);
|
||
|
if (this.workers.size > this.options.minThreads) {
|
||
|
this._removeWorker(workerInfo);
|
||
|
}
|
||
|
}, this.options.idleTimeout).unref();
|
||
|
}
|
||
|
}
|
||
|
runTask(task, options) {
|
||
|
let { filename, name } = options;
|
||
|
const { transferList = [], signal = null } = options;
|
||
|
if (filename == null) {
|
||
|
filename = this.options.filename;
|
||
|
}
|
||
|
if (name == null) {
|
||
|
name = this.options.name;
|
||
|
}
|
||
|
if (typeof filename !== "string") {
|
||
|
return Promise.reject(Errors.FilenameNotProvided());
|
||
|
}
|
||
|
filename = maybeFileURLToPath(filename);
|
||
|
let resolve2;
|
||
|
let reject;
|
||
|
const ret = new Promise((res, rej) => {
|
||
|
resolve2 = res;
|
||
|
reject = rej;
|
||
|
});
|
||
|
const taskInfo = new TaskInfo(task, transferList, filename, name, (err, result) => {
|
||
|
this.completed++;
|
||
|
if (err !== null) {
|
||
|
reject(err);
|
||
|
} else {
|
||
|
resolve2(result);
|
||
|
}
|
||
|
if (this.options.isolateWorkers && taskInfo.workerInfo) {
|
||
|
this._removeWorker(taskInfo.workerInfo);
|
||
|
this._ensureMinimumWorkers();
|
||
|
}
|
||
|
}, signal, this.publicInterface.asyncResource.asyncId());
|
||
|
if (signal !== null) {
|
||
|
if (signal.aborted) {
|
||
|
return Promise.reject(new AbortError());
|
||
|
}
|
||
|
taskInfo.abortListener = () => {
|
||
|
reject(new AbortError());
|
||
|
if (taskInfo.workerInfo !== null) {
|
||
|
this._removeWorker(taskInfo.workerInfo);
|
||
|
this._ensureMinimumWorkers();
|
||
|
} else {
|
||
|
this.taskQueue.remove(taskInfo);
|
||
|
}
|
||
|
};
|
||
|
onabort(signal, taskInfo.abortListener);
|
||
|
}
|
||
|
if (this.taskQueue.size > 0) {
|
||
|
const totalCapacity = this.options.maxQueue + this.pendingCapacity();
|
||
|
if (this.taskQueue.size >= totalCapacity) {
|
||
|
if (this.options.maxQueue === 0) {
|
||
|
return Promise.reject(Errors.NoTaskQueueAvailable());
|
||
|
} else {
|
||
|
return Promise.reject(Errors.TaskQueueAtLimit());
|
||
|
}
|
||
|
} else {
|
||
|
if (this.workers.size < this.options.maxThreads) {
|
||
|
this._addNewWorker();
|
||
|
}
|
||
|
this.taskQueue.push(taskInfo);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
let workerInfo = this.workers.findAvailable();
|
||
|
if (workerInfo !== null && workerInfo.currentUsage() > 0 && signal) {
|
||
|
workerInfo = null;
|
||
|
}
|
||
|
let waitingForNewWorker = false;
|
||
|
if ((workerInfo === null || workerInfo.currentUsage() > 0) && this.workers.size < this.options.maxThreads) {
|
||
|
this._addNewWorker();
|
||
|
waitingForNewWorker = true;
|
||
|
}
|
||
|
if (workerInfo === null) {
|
||
|
if (this.options.maxQueue <= 0 && !waitingForNewWorker) {
|
||
|
return Promise.reject(Errors.NoTaskQueueAvailable());
|
||
|
} else {
|
||
|
this.taskQueue.push(taskInfo);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
const now = performance.now();
|
||
|
taskInfo.started = now;
|
||
|
workerInfo.postTask(taskInfo);
|
||
|
this._maybeDrain();
|
||
|
return ret;
|
||
|
}
|
||
|
pendingCapacity() {
|
||
|
return this.workers.pendingItems.size * this.options.concurrentTasksPerWorker;
|
||
|
}
|
||
|
_maybeDrain() {
|
||
|
if (this.taskQueue.size === 0 && this.skipQueue.length === 0) {
|
||
|
this.publicInterface.emit("drain");
|
||
|
}
|
||
|
}
|
||
|
async destroy() {
|
||
|
while (this.skipQueue.length > 0) {
|
||
|
const taskInfo = this.skipQueue.shift();
|
||
|
taskInfo.done(new Error("Terminating worker thread"));
|
||
|
}
|
||
|
while (this.taskQueue.size > 0) {
|
||
|
const taskInfo = this.taskQueue.shift();
|
||
|
taskInfo.done(new Error("Terminating worker thread"));
|
||
|
}
|
||
|
const exitEvents = [];
|
||
|
while (this.workers.size > 0) {
|
||
|
const [workerInfo] = this.workers;
|
||
|
exitEvents.push(once(workerInfo.worker, "exit"));
|
||
|
this._removeWorker(workerInfo);
|
||
|
}
|
||
|
await Promise.all(exitEvents);
|
||
|
}
|
||
|
};
|
||
|
var _pool;
|
||
|
var Tinypool = class extends EventEmitterAsyncResource_default {
|
||
|
constructor(options = {}) {
|
||
|
if (options.minThreads !== void 0 && options.minThreads > 0 && options.minThreads < 1) {
|
||
|
options.minThreads = Math.max(1, Math.floor(options.minThreads * cpuCount));
|
||
|
}
|
||
|
if (options.maxThreads !== void 0 && options.maxThreads > 0 && options.maxThreads < 1) {
|
||
|
options.maxThreads = Math.max(1, Math.floor(options.maxThreads * cpuCount));
|
||
|
}
|
||
|
super({ ...options, name: "Tinypool" });
|
||
|
__privateAdd(this, _pool, void 0);
|
||
|
if (options.minThreads !== void 0 && options.maxThreads !== void 0 && options.minThreads > options.maxThreads) {
|
||
|
throw new RangeError("options.minThreads and options.maxThreads must not conflict");
|
||
|
}
|
||
|
__privateSet(this, _pool, new ThreadPool(this, options));
|
||
|
}
|
||
|
run(task, options = kDefaultRunOptions) {
|
||
|
const { transferList, filename, name, signal } = options;
|
||
|
return __privateGet(this, _pool).runTask(task, { transferList, filename, name, signal });
|
||
|
}
|
||
|
destroy() {
|
||
|
return __privateGet(this, _pool).destroy();
|
||
|
}
|
||
|
get options() {
|
||
|
return __privateGet(this, _pool).options;
|
||
|
}
|
||
|
get threads() {
|
||
|
const ret = [];
|
||
|
for (const workerInfo of __privateGet(this, _pool).workers) {
|
||
|
ret.push(workerInfo.worker);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
get queueSize() {
|
||
|
const pool = __privateGet(this, _pool);
|
||
|
return Math.max(pool.taskQueue.size - pool.pendingCapacity(), 0);
|
||
|
}
|
||
|
get completed() {
|
||
|
return __privateGet(this, _pool).completed;
|
||
|
}
|
||
|
get duration() {
|
||
|
return performance.now() - __privateGet(this, _pool).start;
|
||
|
}
|
||
|
static get isWorkerThread() {
|
||
|
return process.__tinypool_state__?.isWorkerThread || false;
|
||
|
}
|
||
|
static get workerData() {
|
||
|
return process.__tinypool_state__?.workerData || void 0;
|
||
|
}
|
||
|
static get version() {
|
||
|
const { version } = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
||
|
return version;
|
||
|
}
|
||
|
static move(val) {
|
||
|
if (val != null && typeof val === "object" && typeof val !== "function") {
|
||
|
if (!isTransferable(val)) {
|
||
|
if (types.isArrayBufferView(val)) {
|
||
|
val = new ArrayBufferViewTransferable(val);
|
||
|
} else {
|
||
|
val = new DirectlyTransferable(val);
|
||
|
}
|
||
|
}
|
||
|
markMovable(val);
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
static get transferableSymbol() {
|
||
|
return kTransferable;
|
||
|
}
|
||
|
static get valueSymbol() {
|
||
|
return kValue;
|
||
|
}
|
||
|
static get queueOptionsSymbol() {
|
||
|
return kQueueOptions;
|
||
|
}
|
||
|
};
|
||
|
_pool = new WeakMap();
|
||
|
var _workerId = process.__tinypool_state__?.workerId;
|
||
|
var src_default = Tinypool;
|
||
|
export {
|
||
|
Tinypool,
|
||
|
src_default as default,
|
||
|
isMovable,
|
||
|
isTaskQueue,
|
||
|
isTransferable,
|
||
|
kFieldCount,
|
||
|
kQueueOptions,
|
||
|
kRequestCountField,
|
||
|
kResponseCountField,
|
||
|
kTransferable,
|
||
|
kValue,
|
||
|
markMovable,
|
||
|
_workerId as workerId
|
||
|
};
|