This commit is contained in:
CHEVALLIER Abel
2025-11-13 16:23:22 +01:00
parent de9c515a47
commit cb235644dc
34924 changed files with 3811102 additions and 0 deletions

25
node_modules/piscina/src/task_queue/array_queue.ts generated vendored Normal file
View File

@@ -0,0 +1,25 @@
import assert from 'node:assert';
import type { TaskQueue, Task } from '.';
export class ArrayTaskQueue implements TaskQueue {
tasks: Task[] = []
get size () {
return this.tasks.length;
}
shift (): Task | null {
return this.tasks.shift() ?? null;
}
push (task: Task): void {
this.tasks.push(task);
}
remove (task: Task): void {
const index = this.tasks.indexOf(task);
assert.notStrictEqual(index, -1);
this.tasks.splice(index, 1);
}
}

21
node_modules/piscina/src/task_queue/common.ts generated vendored Normal file
View File

@@ -0,0 +1,21 @@
import type { kQueueOptions } from '../symbols';
export interface TaskQueue {
readonly size: number;
shift(): Task | null;
remove(task: Task): void;
push(task: Task): void;
}
// Public Interface
export interface PiscinaTask extends Task {
taskId: number;
filename: string;
name: string;
created: number;
isAbortable: boolean;
}
export interface Task {
readonly [kQueueOptions]: object | null
};

177
node_modules/piscina/src/task_queue/fixed_queue.ts generated vendored Normal file
View File

@@ -0,0 +1,177 @@
/*
* Modified Fixed Queue Implementation based on the one from Node.js Project
* License: MIT License
* Source: https://github.com/nodejs/node/blob/de7b37880f5a541d5f874c1c2362a65a4be76cd0/lib/internal/fixed_queue.js
*/
import assert from 'node:assert';
import type { Task } from './common';
import { TaskQueue } from '.';
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
const kSize = 2048;
const kMask = kSize - 1;
// The FixedQueue is implemented as a singly-linked list of fixed-size
// circular buffers. It looks something like this:
//
// head tail
// | |
// v v
// +-----------+ <-----\ +-----------+ <------\ +-----------+
// | [null] | \----- | next | \------- | next |
// +-----------+ +-----------+ +-----------+
// | item | <-- bottom | item | <-- bottom | [empty] |
// | item | | item | | [empty] |
// | item | | item | | [empty] |
// | item | | item | | [empty] |
// | item | | item | bottom --> | item |
// | item | | item | | item |
// | ... | | ... | | ... |
// | item | | item | | item |
// | item | | item | | item |
// | [empty] | <-- top | item | | item |
// | [empty] | | item | | item |
// | [empty] | | [empty] | <-- top top --> | [empty] |
// +-----------+ +-----------+ +-----------+
//
// Or, if there is only one circular buffer, it looks something
// like either of these:
//
// head tail head tail
// | | | |
// v v v v
// +-----------+ +-----------+
// | [null] | | [null] |
// +-----------+ +-----------+
// | [empty] | | item |
// | [empty] | | item |
// | item | <-- bottom top --> | [empty] |
// | item | | [empty] |
// | [empty] | <-- top bottom --> | item |
// | [empty] | | item |
// +-----------+ +-----------+
//
// Adding a value means moving `top` forward by one, removing means
// moving `bottom` forward by one. After reaching the end, the queue
// wraps around.
//
// When `top === bottom` the current queue is empty and when
// `top + 1 === bottom` it's full. This wastes a single space of storage
// but allows much quicker checks.
class FixedCircularBuffer {
bottom: number = 0
top: number = 0
list: Array<Task | undefined> = new Array(kSize)
next: FixedCircularBuffer | null = null
isEmpty () {
return this.top === this.bottom;
}
isFull () {
return ((this.top + 1) & kMask) === this.bottom;
}
push (data:Task) {
this.list[this.top] = data;
this.top = (this.top + 1) & kMask;
}
shift () {
const nextItem = this.list[this.bottom];
if (nextItem === undefined) { return null; }
this.list[this.bottom] = undefined;
this.bottom = (this.bottom + 1) & kMask;
return nextItem;
}
remove (task: Task) {
const indexToRemove = this.list.indexOf(task);
assert.notStrictEqual(indexToRemove, -1);
let curr = indexToRemove;
while (true) {
const next = (curr + 1) & kMask;
this.list[curr] = this.list[next];
if (this.list[curr] === undefined) {
break;
}
if (next === indexToRemove) {
this.list[curr] = undefined;
break;
}
curr = next;
}
this.top = (this.top - 1) & kMask;
}
}
export class FixedQueue implements TaskQueue {
head: FixedCircularBuffer
tail: FixedCircularBuffer
#size: number = 0
constructor () {
this.head = this.tail = new FixedCircularBuffer();
}
isEmpty () {
return this.head.isEmpty();
}
push (data:Task) {
if (this.head.isFull()) {
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
// and sets it as the new main queue.
this.head = this.head.next = new FixedCircularBuffer();
}
this.head.push(data);
this.#size++;
}
shift (): Task | null {
const tail = this.tail;
const next = tail.shift();
if (next !== null) this.#size--;
if (tail.isEmpty() && tail.next !== null) {
// If there is another queue, it forms the new tail.
this.tail = tail.next;
tail.next = null;
}
return next;
}
remove (task: Task) {
let prev: FixedCircularBuffer | null = null;
let buffer = this.tail;
while (true) {
if (buffer.list.includes(task)) {
buffer.remove(task);
this.#size--;
break;
}
if (buffer.next === null) break;
prev = buffer;
buffer = buffer.next;
}
if (buffer.isEmpty()) {
// removing tail
if (prev === null) {
// if tail is not the last buffer
if (buffer.next !== null) this.tail = buffer.next;
} else {
// removing head
if (buffer.next === null) {
this.head = prev;
} else {
// removing buffer from middle
prev.next = buffer.next;
}
}
}
}
get size () {
return this.#size;
}
};

148
node_modules/piscina/src/task_queue/index.ts generated vendored Normal file
View File

@@ -0,0 +1,148 @@
import type { MessagePort } from 'node:worker_threads';
import { performance } from 'node:perf_hooks';
import { AsyncResource } from 'node:async_hooks';
import type { WorkerInfo } from '../worker_pool';
import type { AbortSignalAny, AbortSignalEventEmitter } from '../abort';
import { isMovable } from '../common';
import { kTransferable, kValue, kQueueOptions } from '../symbols';
import type { Task, TaskQueue, PiscinaTask } from './common';
export { ArrayTaskQueue } from './array_queue';
export { FixedQueue } from './fixed_queue';
export type TaskCallback = (err: Error, result: any) => void
// Grab the type of `transferList` off `MessagePort`. At the time of writing,
// only ArrayBuffer and MessagePort are valid, but let's avoid having to update
// our types here every time Node.js adds support for more objects.
export type TransferList = MessagePort extends {
postMessage: (value: any, transferList: infer T) => any
}
? T
: never
export type TransferListItem = TransferList extends Array<infer T> ? T : never
/**
* Verifies if a given TaskQueue is valid
*
* @export
* @param {*} value
* @return {*} {boolean}
*/
export function isTaskQueue (value: TaskQueue): boolean {
return (
typeof value === 'object' &&
value !== null &&
'size' in value &&
typeof value.shift === 'function' &&
typeof value.remove === 'function' &&
typeof value.push === 'function'
);
}
let taskIdCounter = 0;
// Extend AsyncResource so that async relations between posting a task and
// receiving its result are visible to diagnostic tools.
export class TaskInfo extends AsyncResource implements Task {
callback : TaskCallback;
task : any;
transferList : TransferList;
filename : string;
name : string;
taskId : number;
abortSignal : AbortSignalAny | null;
// abortListener : (() => void) | null = null;
workerInfo : WorkerInfo | null = null;
created : number;
started : number;
aborted = false;
_abortListener: (() => void) | null = null;
constructor (
task : any,
transferList : TransferList,
filename : string,
name : string,
callback : TaskCallback,
abortSignal : AbortSignalAny | null,
triggerAsyncId : number) {
super('Piscina.Task', { requireManualDestroy: true, triggerAsyncId });
this.callback = callback;
this.task = task;
this.transferList = transferList;
// If the task is a Transferable returned by
// Piscina.move(), then add it to the transferList
// automatically
if (isMovable(task)) {
// This condition should never be hit but typescript
// complains if we dont do the check.
/* istanbul ignore if */
if (this.transferList == null) {
this.transferList = [];
}
this.transferList =
this.transferList.concat(task[kTransferable]);
this.task = task[kValue];
}
this.filename = filename;
this.name = name;
// TODO: This should not be global
this.taskId = taskIdCounter++;
this.abortSignal = abortSignal;
this.created = performance.now();
this.started = 0;
}
// TODO: improve this handling - ideally should be extended
set abortListener (value: (() => void)) {
this._abortListener = () => {
this.aborted = true;
value();
};
}
get abortListener (): (() => void) | null {
return this._abortListener;
}
releaseTask () : any {
const ret = this.task;
this.task = null;
return ret;
}
done (err : Error | null, result? : any) : void {
this.runInAsyncScope(this.callback, null, err, result);
this.emitDestroy(); // `TaskInfo`s are used only once.
// If an abort signal was used, remove the listener from it when
// done to make sure we do not accidentally leak.
if (this.abortSignal && this.abortListener) {
if ('removeEventListener' in this.abortSignal && this.abortListener) {
this.abortSignal.removeEventListener('abort', this.abortListener);
} else {
(this.abortSignal as AbortSignalEventEmitter).off(
'abort', this.abortListener);
}
}
}
get [kQueueOptions] () : {} | null {
return this.task?.[kQueueOptions] ?? null;
}
get interface (): PiscinaTask {
return {
taskId: this.taskId,
filename: this.filename,
name: this.name,
created: this.created,
isAbortable: this.abortSignal !== null,
[kQueueOptions]: this[kQueueOptions]
};
}
}
export { Task, TaskQueue, PiscinaTask };