Updated the files.

This commit is contained in:
Batuhan Berk Başoğlu 2024-02-08 19:38:41 -05:00
parent 1553e6b971
commit 753967d4f5
23418 changed files with 3784666 additions and 0 deletions

233
my-app/node_modules/piscina/test/abort-task.ts generated vendored Executable file
View file

@ -0,0 +1,233 @@
import { EventEmitter } from 'events';
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('tasks can be aborted through AbortController while running', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/notify-then-sleep.ts')
});
const buf = new Int32Array(new SharedArrayBuffer(4));
const abortController = new AbortController();
rejects(pool.runTask(buf, abortController.signal),
/The task has been aborted/);
Atomics.wait(buf, 0, 0);
equal(Atomics.load(buf, 0), 1);
abortController.abort();
});
test('tasks can be aborted through EventEmitter while running', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/notify-then-sleep.ts')
});
const buf = new Int32Array(new SharedArrayBuffer(4));
const ee = new EventEmitter();
rejects(pool.runTask(buf, ee), /The task has been aborted/);
rejects(pool.run(buf, { signal: ee }), /The task has been aborted/);
Atomics.wait(buf, 0, 0);
equal(Atomics.load(buf, 0), 1);
ee.emit('abort');
});
test('tasks can be aborted through EventEmitter before running', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
maxThreads: 1
});
const bufs = [
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4))
];
const task1 = pool.runTask(bufs[0]);
const ee = new EventEmitter();
rejects(pool.runTask(bufs[1], ee), /The task has been aborted/);
rejects(pool.run(bufs[1], { signal: ee }), /The task has been aborted/);
equal(pool.queueSize, 2);
ee.emit('abort');
// Wake up the thread handling the first task.
Atomics.store(bufs[0], 0, 1);
Atomics.notify(bufs[0], 0, 1);
await task1;
});
test('abortable tasks will not share workers (abortable posted second)', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
maxThreads: 1,
concurrentTasksPerWorker: 2
});
const bufs = [
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4))
];
const task1 = pool.runTask(bufs[0]);
const ee = new EventEmitter();
rejects(pool.runTask(bufs[1], ee), /The task has been aborted/);
equal(pool.queueSize, 1);
ee.emit('abort');
// Wake up the thread handling the first task.
Atomics.store(bufs[0], 0, 1);
Atomics.notify(bufs[0], 0, 1);
await task1;
});
test('abortable tasks will not share workers (abortable posted first)', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
maxThreads: 1,
concurrentTasksPerWorker: 2
});
const ee = new EventEmitter();
rejects(pool.runTask('while(true);', ee), /The task has been aborted/);
const task2 = pool.runTask('42');
equal(pool.queueSize, 1);
ee.emit('abort');
// Wake up the thread handling the second task.
equal(await task2, 42);
});
test('abortable tasks will not share workers (on worker available)', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/sleep.js'),
maxThreads: 1,
concurrentTasksPerWorker: 2
});
// Task 1 will sleep 100 ms then complete,
// Task 2 will sleep 300 ms then complete.
// Abortable task 3 should still be in the queue
// when Task 1 completes, but should not be selected
// until after Task 2 completes because it is abortable.
const ret = await Promise.all([
pool.runTask({ time: 100, a: 1 }),
pool.runTask({ time: 300, a: 2 }),
pool.runTask({ time: 100, a: 3 }, new EventEmitter())
]);
equal(ret[0], 0);
equal(ret[1], 1);
equal(ret[2], 2);
});
test('abortable tasks will not share workers (destroy workers)', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/sleep.js'),
maxThreads: 1,
concurrentTasksPerWorker: 2
});
// Task 1 will sleep 100 ms then complete,
// Task 2 will sleep 300 ms then complete.
// Abortable task 3 should still be in the queue
// when Task 1 completes, but should not be selected
// until after Task 2 completes because it is abortable.
pool.runTask({ time: 100, a: 1 }).then(() => {
pool.destroy();
});
rejects(pool.runTask({ time: 300, a: 2 }), /Terminating worker thread/);
rejects(pool.runTask({ time: 100, a: 3 }, new EventEmitter()),
/Terminating worker thread/);
});
test('aborted AbortSignal rejects task immediately', async ({ rejects, equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/move.ts')
});
const controller = new AbortController();
// Abort the controller early
controller.abort();
equal(controller.signal.aborted, true);
// The data won't be moved because the task will abort immediately.
const data = new Uint8Array(new SharedArrayBuffer(4));
rejects(pool.runTask(data, [data.buffer], controller.signal),
/The task has been aborted/);
equal(data.length, 4);
});
test('task with AbortSignal cleans up properly', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const ee = new EventEmitter();
await pool.runTask('1+1', ee);
const { getEventListeners } = EventEmitter as any;
if (typeof getEventListeners === 'function') {
equal(getEventListeners(ee, 'abort').length, 0);
}
const controller = new AbortController();
await pool.runTask('1+1', controller.signal);
});
test('aborted AbortSignal rejects task immediately (with reason)', async ({ match, equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/move.ts')
});
const customReason = new Error('custom reason');
const controller = new AbortController();
controller.abort(customReason);
equal(controller.signal.aborted, true);
equal(controller.signal.reason, customReason);
// The data won't be moved because the task will abort immediately.
const data = new Uint8Array(new SharedArrayBuffer(4));
try {
await pool.run(data, { transferList: [data.buffer], signal: controller.signal });
} catch (error) {
equal(error.message, 'The task has been aborted');
match(error.cause, customReason);
}
equal(data.length, 4);
});
test('tasks can be aborted through AbortController while running', async ({ equal, match }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/notify-then-sleep.ts')
});
const reason = new Error('custom reason');
const buf = new Int32Array(new SharedArrayBuffer(4));
const abortController = new AbortController();
try {
const promise = pool.run(buf, { signal: abortController.signal });
Atomics.wait(buf, 0, 0);
equal(Atomics.load(buf, 0), 1);
abortController.abort(reason);
await promise;
} catch (error) {
equal(error.message, 'The task has been aborted');
match(error.cause, reason);
}
});

43
my-app/node_modules/piscina/test/async-context.ts generated vendored Executable file
View file

@ -0,0 +1,43 @@
import { createHook, executionAsyncId } from 'async_hooks';
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('postTask() calls the correct async hooks', async ({ equal }) => {
let taskId;
let initCalls = 0;
let beforeCalls = 0;
let afterCalls = 0;
let resolveCalls = 0;
const hook = createHook({
init (id, type) {
if (type === 'Piscina.Task') {
initCalls++;
taskId = id;
}
},
before (id) {
if (id === taskId) beforeCalls++;
},
after (id) {
if (id === taskId) afterCalls++;
},
promiseResolve () {
if (executionAsyncId() === taskId) resolveCalls++;
}
});
hook.enable();
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
await pool.runTask('42');
hook.disable();
equal(initCalls, 1);
equal(beforeCalls, 1);
equal(afterCalls, 1);
equal(resolveCalls, 1);
});

81
my-app/node_modules/piscina/test/atomics-optimization.ts generated vendored Executable file
View file

@ -0,0 +1,81 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('coverage test for Atomics optimization', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/notify-then-sleep-or.ts'),
minThreads: 2,
maxThreads: 2,
concurrentTasksPerWorker: 2
});
const tasks = [];
let v : number;
// Post 4 tasks, and wait for all of them to be ready.
const i32array = new Int32Array(new SharedArrayBuffer(4));
for (let index = 0; index < 4; index++) {
tasks.push(pool.runTask({ i32array, index }));
}
// Wait for 2 tasks to enter 'wait' state.
do {
v = Atomics.load(i32array, 0);
if (popcount8(v) >= 2) break;
Atomics.wait(i32array, 0, v);
} while (true);
// The check above could also be !== 2 but it's hard to get things right
// sometimes and this gives us a nice assertion. Basically, at this point
// exactly 2 tasks should be in Atomics.wait() state.
equal(popcount8(v), 2);
// Wake both tasks up as simultaneously as possible. The other 2 tasks should
// then start executing.
Atomics.store(i32array, 0, 0);
Atomics.notify(i32array, 0, Infinity);
// Wait for the other 2 tasks to enter 'wait' state.
do {
v = Atomics.load(i32array, 0);
if (popcount8(v) >= 2) break;
Atomics.wait(i32array, 0, v);
} while (true);
// At this point, the first two tasks are definitely finished and have
// definitely posted results back to the main thread, and the main thread
// has definitely not received them yet, meaning that the Atomics check will
// be used. Making sure that that works is the point of this test.
// Wake up the remaining 2 tasks in order to make sure that the test finishes.
// Do the same consistency check beforehand as above.
equal(popcount8(v), 2);
Atomics.store(i32array, 0, 0);
Atomics.notify(i32array, 0, Infinity);
await Promise.all(tasks);
});
// Inefficient but straightforward 8-bit popcount
function popcount8 (v : number) : number {
v &= 0xff;
if (v & 0b11110000) return popcount8(v >>> 4) + popcount8(v & 0xb00001111);
if (v & 0b00001100) return popcount8(v >>> 2) + popcount8(v & 0xb00000011);
if (v & 0b00000010) return popcount8(v >>> 1) + popcount8(v & 0xb00000001);
return v;
}
test('avoids unbounded recursion', async () => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-isworkerthread.ts'),
minThreads: 2,
maxThreads: 2
});
const tasks = [];
for (let i = 1; i <= 10000; i++) {
tasks.push(pool.runTask(null));
}
await Promise.all(tasks);
});

17
my-app/node_modules/piscina/test/console-log.ts generated vendored Executable file
View file

@ -0,0 +1,17 @@
import concat from 'concat-stream';
import { spawn } from 'child_process';
import { resolve } from 'path';
import { test } from 'tap';
test('console.log() calls are not blocked by Atomics.wait()', async ({ equal }) => {
const proc = spawn(process.execPath, [
...process.execArgv, resolve(__dirname, 'fixtures/console-log.ts')
], {
stdio: ['inherit', 'pipe', 'inherit']
});
const data = await new Promise((resolve) => {
proc.stdout.setEncoding('utf8').pipe(concat(resolve));
});
equal(data, 'A\nB\n');
});

9
my-app/node_modules/piscina/test/fixtures/console-log.ts generated vendored Executable file
View file

@ -0,0 +1,9 @@
import Piscina from '../..';
import { resolve } from 'path';
const pool = new Piscina({
filename: resolve(__dirname, 'eval.js'),
maxThreads: 1
});
pool.runTask('console.log("A"); console.log("B");');

12
my-app/node_modules/piscina/test/fixtures/esm-async.mjs generated vendored Executable file
View file

@ -0,0 +1,12 @@
import util from 'util';
const sleep = util.promisify(setTimeout);
// eslint-disable-next-line no-eval
function handler (code) { return eval(code); }
async function load () {
await sleep(100);
return handler;
}
export default load();

2
my-app/node_modules/piscina/test/fixtures/esm-export.mjs generated vendored Executable file
View file

@ -0,0 +1,2 @@
// eslint-disable-next-line no-eval
export default function (code) { return eval(code); };

14
my-app/node_modules/piscina/test/fixtures/eval-async.js generated vendored Executable file
View file

@ -0,0 +1,14 @@
'use strict';
const { promisify } = require('util');
const sleep = promisify(setTimeout);
// eslint-disable-next-line no-eval
function handler (code) { return eval(code); }
async function load () {
await sleep(100);
return handler;
}
module.exports = load();

2
my-app/node_modules/piscina/test/fixtures/eval.js generated vendored Executable file
View file

@ -0,0 +1,2 @@
// eslint-disable-next-line no-eval
module.exports = function (code) { return eval(code); };

10
my-app/node_modules/piscina/test/fixtures/move.ts generated vendored Executable file
View file

@ -0,0 +1,10 @@
import Piscina from '../..';
import assert from 'assert';
import { types } from 'util';
export default function (moved) {
if (moved !== undefined) {
assert(types.isAnyArrayBuffer(moved));
}
return Piscina.move(new ArrayBuffer(10));
}

10
my-app/node_modules/piscina/test/fixtures/multiple.js generated vendored Executable file
View file

@ -0,0 +1,10 @@
'use strict';
function a () { return 'a'; }
function b () { return 'b'; }
a.a = a;
a.b = b;
module.exports = a;

View file

@ -0,0 +1,10 @@
// Set the index-th bith in i32array[0], then wait for it to be un-set again.
module.exports = function ({ i32array, index }) {
Atomics.or(i32array, 0, 1 << index);
Atomics.notify(i32array, 0, Infinity);
do {
const v = Atomics.load(i32array, 0);
if (!(v & (1 << index))) break;
Atomics.wait(i32array, 0, v);
} while (true);
};

View file

@ -0,0 +1,5 @@
module.exports = function (i32array) {
Atomics.store(i32array, 0, 1);
Atomics.notify(i32array, 0, Infinity);
Atomics.wait(i32array, 0, 1);
};

View file

@ -0,0 +1,8 @@
'use strict';
module.exports = () => {
const array = [];
while (true) {
array.push([array]);
}
};

View file

@ -0,0 +1,18 @@
'use strict';
const Piscina = require('../../dist/src');
let time;
module.exports = {
send: async () => {
const data = new ArrayBuffer(128);
try {
return Piscina.move(data);
} finally {
setTimeout(() => { time = data.byteLength; }, 1000);
}
},
get: () => {
return time;
}
};

View file

@ -0,0 +1,38 @@
'use strict';
const Piscina = require('../../dist/src');
class Shared {
constructor (data) {
this.name = 'shared';
this.data = data;
}
get [Piscina.transferableSymbol] () {
return [this.data];
}
get [Piscina.valueSymbol] () {
return { name: this.name, data: this.data };
}
make () {
return Piscina.move(this);
}
}
let time;
module.exports = {
send: async () => {
const data = new ArrayBuffer(128);
const shared = new Shared(data);
try {
return shared.make();
} finally {
setTimeout(() => { time = data.byteLength; }, 1000);
}
},
get: () => {
return time;
}
};

View file

@ -0,0 +1,6 @@
import Piscina from '../..';
import assert from 'assert';
assert.strictEqual(Piscina.isWorkerThread, true);
export default function () { return 'done'; }

View file

@ -0,0 +1,6 @@
import Piscina from '../..';
import assert from 'assert';
assert.strictEqual(Piscina.workerData, 'ABC');
export default function () { return 'done'; }

12
my-app/node_modules/piscina/test/fixtures/sleep.js generated vendored Executable file
View file

@ -0,0 +1,12 @@
'use strict';
const { promisify } = require('util');
const sleep = promisify(setTimeout);
const buf = new Uint32Array(new SharedArrayBuffer(4));
module.exports = async ({ time = 100, a }) => {
await sleep(time);
const ret = Atomics.exchange(buf, 0, a);
return ret;
};

View file

@ -0,0 +1,5 @@
module.exports = function (i32array) {
Atomics.wait(i32array, 0, 0);
Atomics.store(i32array, 0, -1);
Atomics.notify(i32array, 0, Infinity);
};

11
my-app/node_modules/piscina/test/fixtures/wait-for-others.ts generated vendored Executable file
View file

@ -0,0 +1,11 @@
import { threadId } from 'worker_threads';
module.exports = async function ([i32array, n]) {
Atomics.add(i32array, 0, 1);
Atomics.notify(i32array, 0, Infinity);
let lastSeenValue;
while ((lastSeenValue = Atomics.load(i32array, 0)) < n) {
Atomics.wait(i32array, 0, lastSeenValue);
}
return threadId;
};

31
my-app/node_modules/piscina/test/histogram.ts generated vendored Executable file
View file

@ -0,0 +1,31 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('pool will maintain run and wait time histograms', async ({ equal, ok }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const tasks = [];
for (let n = 0; n < 10; n++) {
tasks.push(pool.runTask('42'));
}
await Promise.all(tasks);
const waitTime = pool.waitTime as any;
ok(waitTime);
equal(typeof waitTime.average, 'number');
equal(typeof waitTime.mean, 'number');
equal(typeof waitTime.stddev, 'number');
equal(typeof waitTime.min, 'number');
equal(typeof waitTime.max, 'number');
const runTime = pool.runTime as any;
ok(runTime);
equal(typeof runTime.average, 'number');
equal(typeof runTime.mean, 'number');
equal(typeof runTime.stddev, 'number');
equal(typeof runTime.min, 'number');
equal(typeof runTime.max, 'number');
});

44
my-app/node_modules/piscina/test/idle-timeout.ts generated vendored Executable file
View file

@ -0,0 +1,44 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
import { promisify } from 'util';
const delay = promisify(setTimeout);
test('idle timeout will let go of threads early', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-others.ts'),
idleTimeout: 500,
minThreads: 1,
maxThreads: 2
});
equal(pool.threads.length, 1);
const buffer = new Int32Array(new SharedArrayBuffer(4));
const firstTasks = [
pool.runTask([buffer, 2]),
pool.runTask([buffer, 2])
];
equal(pool.threads.length, 2);
const earlyThreadIds = await Promise.all(firstTasks);
equal(pool.threads.length, 2);
await delay(2000);
equal(pool.threads.length, 1);
const secondTasks = [
pool.runTask([buffer, 4]),
pool.runTask([buffer, 4])
];
equal(pool.threads.length, 2);
const lateThreadIds = await Promise.all(secondTasks);
// One thread should have been idle in between and exited, one should have
// been reused.
equal(earlyThreadIds.length, 2);
equal(lateThreadIds.length, 2);
equal(new Set([...earlyThreadIds, ...lateThreadIds]).size, 3);
});

21
my-app/node_modules/piscina/test/load-with-esm.ts generated vendored Executable file
View file

@ -0,0 +1,21 @@
import { test } from 'tap';
const importESM : (specifier : string) => Promise<any> =
// eslint-disable-next-line no-eval
eval('(specifier) => import(specifier)');
test('Piscina is default export', {}, async ({ equal }) => {
equal((await importESM('piscina')).default, require('../'));
});
test('Exports match own property names', {}, async ({ strictSame }) => {
// Check that version, workerData, etc. are re-exported.
const exported = new Set(Object.getOwnPropertyNames(await importESM('piscina')));
const required = new Set(Object.getOwnPropertyNames(require('../')));
// Remove constructor properties + default export.
for (const k of ['prototype', 'length', 'name']) required.delete(k);
exported.delete('default');
strictSame(exported, required);
});

19
my-app/node_modules/piscina/test/messages.ts generated vendored Executable file
View file

@ -0,0 +1,19 @@
import Piscina from '../dist/src';
import { test } from 'tap';
import { resolve } from 'path';
import { once } from 'events';
test('Pool receive message from workers', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const messagePromise = once(pool, 'message');
const taskResult = pool.runTask(`
require('worker_threads').parentPort.postMessage("some message");
42
`);
equal(await taskResult, 42);
equal((await messagePromise)[0], 'some message');
});

107
my-app/node_modules/piscina/test/move-test.ts generated vendored Executable file
View file

@ -0,0 +1,107 @@
import Piscina from '..';
import {
isMovable,
markMovable,
isTransferable
} from '../dist/src/common';
import { test } from 'tap';
import { types } from 'util';
import { MessageChannel, MessagePort } from 'worker_threads';
import { resolve } from 'path';
const {
transferableSymbol,
valueSymbol
} = Piscina;
test('Marking an object as movable works as expected', async ({ ok }) => {
const obj : any = {
get [transferableSymbol] () : object { return {}; },
get [valueSymbol] () : object { return {}; }
};
ok(isTransferable(obj));
ok(!isMovable(obj)); // It's not movable initially
markMovable(obj);
ok(isMovable(obj)); // It is movable now
});
test('Marking primitives and null works as expected', async ({ equal }) => {
equal(Piscina.move(null), null);
equal(Piscina.move(1 as any), 1);
equal(Piscina.move(false as any), false);
equal(Piscina.move('test' as any), 'test');
});
test('Using Piscina.move() returns a movable object', async ({ ok }) => {
const obj : any = {
get [transferableSymbol] () : object { return {}; },
get [valueSymbol] () : object { return {}; }
};
ok(!isMovable(obj)); // It's not movable initially
const movable = Piscina.move(obj);
ok(isMovable(movable)); // It is movable now
});
test('Using ArrayBuffer works as expected', async ({ ok, equal }) => {
const ab = new ArrayBuffer(5);
const movable = Piscina.move(ab);
ok(isMovable(movable));
ok(types.isAnyArrayBuffer(movable[valueSymbol]));
ok(types.isAnyArrayBuffer(movable[transferableSymbol]));
equal(movable[transferableSymbol], ab);
});
test('Using TypedArray works as expected', async ({ ok, equal }) => {
const ab = new Uint8Array(5);
const movable = Piscina.move(ab);
ok(isMovable(movable));
ok((types as any).isArrayBufferView(movable[valueSymbol]));
ok(types.isAnyArrayBuffer(movable[transferableSymbol]));
equal(movable[transferableSymbol], ab.buffer);
});
test('Using MessagePort works as expected', async ({ ok, equal }) => {
const mc = new MessageChannel();
const movable = Piscina.move(mc.port1);
ok(isMovable(movable));
ok(movable[valueSymbol] instanceof MessagePort);
ok(movable[transferableSymbol] instanceof MessagePort);
equal(movable[transferableSymbol], mc.port1);
});
test('Moving works', async ({ equal, ok }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/move.ts')
});
{
const ab = new ArrayBuffer(10);
const ret = await pool.runTask(Piscina.move(ab));
equal(ab.byteLength, 0); // It was moved
ok(types.isAnyArrayBuffer(ret));
}
{
// Test with empty transferList
const ab = new ArrayBuffer(10);
const ret = await pool.runTask(Piscina.move(ab), []);
equal(ab.byteLength, 0); // It was moved
ok(types.isAnyArrayBuffer(ret));
}
{
// Test with empty transferList
const ab = new ArrayBuffer(10);
const ret = await pool.run(Piscina.move(ab));
equal(ab.byteLength, 0); // It was moved
ok(types.isAnyArrayBuffer(ret));
}
{
// Test with empty transferList
const ab = new ArrayBuffer(10);
const ret = await pool.run(Piscina.move(ab), { transferList: [] });
equal(ab.byteLength, 0); // It was moved
ok(types.isAnyArrayBuffer(ret));
}
});

31
my-app/node_modules/piscina/test/nice.ts generated vendored Executable file
View file

@ -0,0 +1,31 @@
import Piscina from '..';
import { resolve } from 'path';
import { test } from 'tap';
test('can set niceness for threads on Linux', {
skip: process.platform !== 'linux'
}, async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
niceIncrement: 5
});
// ts-ignore because the dependency is not installed on Windows.
// @ts-ignore
const currentNiceness = (await import('nice-napi')).default(0);
const result = await worker.runTask('require("nice-napi")()');
// niceness is capped to 19 on Linux.
const expected = Math.min(currentNiceness + 5, 19);
equal(result, expected);
});
test('setting niceness never does anything bad', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
niceIncrement: 5
});
const result = await worker.runTask('42');
equal(result, 42);
});

129
my-app/node_modules/piscina/test/option-validation.ts generated vendored Executable file
View file

@ -0,0 +1,129 @@
import Piscina from '..';
import { test } from 'tap';
test('filename cannot be non-null/non-string', async ({ throws }) => {
throws(() => new Piscina(({
filename: 12
}) as any), /options.filename must be a string or null/);
});
test('name cannot be non-null/non-string', async ({ throws }) => {
throws(() => new Piscina(({
name: 12
}) as any), /options.name must be a string or null/);
});
test('minThreads must be non-negative integer', async ({ throws }) => {
throws(() => new Piscina(({
minThreads: -1
}) as any), /options.minThreads must be a non-negative integer/);
throws(() => new Piscina(({
minThreads: 'string'
}) as any), /options.minThreads must be a non-negative integer/);
});
test('maxThreads must be positive integer', async ({ throws }) => {
throws(() => new Piscina(({
maxThreads: -1
}) as any), /options.maxThreads must be a positive integer/);
throws(() => new Piscina(({
maxThreads: 0
}) as any), /options.maxThreads must be a positive integer/);
throws(() => new Piscina(({
maxThreads: 'string'
}) as any), /options.maxThreads must be a positive integer/);
});
test('concurrentTasksPerWorker must be positive integer', async ({ throws }) => {
throws(() => new Piscina(({
concurrentTasksPerWorker: -1
}) as any), /options.concurrentTasksPerWorker must be a positive integer/);
throws(() => new Piscina(({
concurrentTasksPerWorker: 0
}) as any), /options.concurrentTasksPerWorker must be a positive integer/);
throws(() => new Piscina(({
concurrentTasksPerWorker: 'string'
}) as any), /options.concurrentTasksPerWorker must be a positive integer/);
});
test('idleTimeout must be non-negative integer', async ({ throws }) => {
throws(() => new Piscina(({
idleTimeout: -1
}) as any), /options.idleTimeout must be a non-negative integer/);
throws(() => new Piscina(({
idleTimeout: 'string'
}) as any), /options.idleTimeout must be a non-negative integer/);
});
test('maxQueue must be non-negative integer', async ({ throws, equal }) => {
throws(() => new Piscina(({
maxQueue: -1
}) as any), /options.maxQueue must be a non-negative integer/);
throws(() => new Piscina(({
maxQueue: 'string'
}) as any), /options.maxQueue must be a non-negative integer/);
const p = new Piscina({ maxQueue: 'auto', maxThreads: 2 });
equal(p.options.maxQueue, 4);
});
test('useAtomics must be a boolean', async ({ throws }) => {
throws(() => new Piscina(({
useAtomics: -1
}) as any), /options.useAtomics must be a boolean/);
throws(() => new Piscina(({
useAtomics: 'string'
}) as any), /options.useAtomics must be a boolean/);
});
test('resourceLimits must be an object', async ({ throws }) => {
throws(() => new Piscina(({
resourceLimits: 0
}) as any), /options.resourceLimits must be an object/);
});
test('taskQueue must be a TaskQueue object', async ({ throws }) => {
throws(() => new Piscina(({
taskQueue: 0
}) as any), /options.taskQueue must be a TaskQueue object/);
throws(() => new Piscina(({
taskQueue: 'test'
}) as any), /options.taskQueue must be a TaskQueue object/);
throws(() => new Piscina(({
taskQueue: null
}) as any), /options.taskQueue must be a TaskQueue object/);
throws(() => new Piscina(({
taskQueue: new Date()
}) as any), /options.taskQueue must be a TaskQueue object/);
throws(() => new Piscina(({
taskQueue: { } as any
}) as any), /options.taskQueue must be a TaskQueue object/);
});
test('niceIncrement must be non-negative integer', async ({ throws }) => {
throws(() => new Piscina(({
niceIncrement: -1
}) as any), /options.niceIncrement must be a non-negative integer/);
throws(() => new Piscina(({
niceIncrement: 'string'
}) as any), /options.niceIncrement must be a non-negative integer/);
});
test('trackUnmanagedFds must be a boolean', async ({ throws }) => {
throws(() => new Piscina(({
trackUnmanagedFds: -1
}) as any), /options.trackUnmanagedFds must be a boolean/);
throws(() => new Piscina(({
trackUnmanagedFds: 'string'
}) as any), /options.trackUnmanagedFds must be a boolean/);
});

99
my-app/node_modules/piscina/test/pool-close.ts generated vendored Executable file
View file

@ -0,0 +1,99 @@
import { test } from 'tap';
import Piscina from '..';
import { resolve } from 'path';
import { once } from 'events';
test('close()', async (t) => {
t.test('no pending tasks', async (t) => {
const pool = new Piscina({ filename: resolve(__dirname, 'fixtures/sleep.js') });
await pool.close();
t.pass('pool closed successfully');
});
t.test('queued tasks waits for all tasks to complete', async (t) => {
const pool = new Piscina({ filename: resolve(__dirname, 'fixtures/sleep.js'), maxThreads: 1 });
const task1 = pool.run({ time: 100 });
const task2 = pool.run({ time: 100 });
setImmediate(() => t.resolves(pool.close(), 'close is resolved when all running tasks are completed'));
await Promise.all([
t.resolves(once(pool, 'close'), 'handler is called when pool is closed'),
t.resolves(task1, 'complete running task'),
t.resolves(task2, 'complete running task')
]);
});
t.test('abort any task enqueued during closing up', async (t) => {
const pool = new Piscina({ filename: resolve(__dirname, 'fixtures/sleep.js'), maxThreads: 1 });
setImmediate(() => {
t.resolves(pool.close(), 'close is resolved when running tasks are completed');
t.resolves(pool.run({ time: 1000 }).then(null, err => {
t.equal(err.message, 'The task has been aborted');
t.equal(err.cause, 'queue is closing up');
}));
});
await t.resolves(pool.run({ time: 100 }), 'complete running task');
});
});
test('close({force: true})', async (t) => {
t.test('queued tasks waits for all tasks already running and aborts tasks that are not started yet', async (t) => {
const pool = new Piscina({ filename: resolve(__dirname, 'fixtures/sleep.js'), maxThreads: 1, concurrentTasksPerWorker: 1 });
const task1 = pool.run({ time: 1000 });
const task2 = pool.run({ time: 200 });
setImmediate(() => t.resolves(pool.close({ force: true }), 'close is resolved when all running tasks are completed'));
await Promise.all([
t.resolves(once(pool, 'close'), 'handler is called when pool is closed'),
t.resolves(task1, 'complete running task'),
t.resolves(task2.then(null, err => {
t.equal(err.message, 'The task has been aborted');
t.equal(err.cause, 'pool is closed');
}))
]);
});
t.test('queued tasks waits for all tasks already running and aborts tasks that are not started yet', async (t) => {
const pool = new Piscina({ filename: resolve(__dirname, 'fixtures/sleep.js'), maxThreads: 1, concurrentTasksPerWorker: 2 });
const task1 = pool.run({ time: 500 });
const task2 = pool.run({ time: 100 });
const task3 = pool.run({ time: 100 });
const task4 = pool.run({ time: 100 });
setImmediate(() => t.resolves(pool.close({ force: true }), 'close is resolved when all running tasks are completed'));
await Promise.all([
t.resolves(once(pool, 'close'), 'handler is called when pool is closed'),
t.resolves(task1, 'complete running task'),
t.resolves(task2, 'complete running task'),
t.rejects(task3, /The task has been aborted/, 'abort task that are not started yet'),
t.rejects(task4, /The task has been aborted/, 'abort task that are not started yet')
]);
});
});
test('timed out close operation destroys the pool', async (t) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/sleep.js'),
maxThreads: 1,
closeTimeout: 500
});
const task1 = pool.run({ time: 5000 });
const task2 = pool.run({ time: 5000 });
setImmediate(() => t.resolves(pool.close(), 'close is resolved on timeout'));
await Promise.all([
t.resolves(once(pool, 'error'), 'error handler is called on timeout'),
t.rejects(task1, /Terminating worker thread/, 'task is aborted due to timeout'),
t.rejects(task2, /Terminating worker thread/, 'task is aborted due to timeout')
]);
});

11
my-app/node_modules/piscina/test/pool-destroy.ts generated vendored Executable file
View file

@ -0,0 +1,11 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('can destroy pool while tasks are running', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
setImmediate(() => pool.destroy());
await rejects(pool.runTask('while(1){}'), /Terminating worker thread/);
});

207
my-app/node_modules/piscina/test/post-task.ts generated vendored Executable file
View file

@ -0,0 +1,207 @@
import { MessageChannel } from 'worker_threads';
import { cpus } from 'os';
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('postTask() can transfer ArrayBuffer instances', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-isworkerthread.ts')
});
const ab = new ArrayBuffer(40);
await pool.runTask({ ab }, [ab]);
equal(pool.completed, 1);
equal(ab.byteLength, 0);
});
test('postTask() can transfer ArrayBuffer instances', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-isworkerthread.ts')
});
const ab = new ArrayBuffer(40);
await pool.run({ ab }, { transferList: [ab] });
equal(pool.completed, 1);
equal(ab.byteLength, 0);
});
test('postTask() cannot clone build-in objects', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-isworkerthread.ts')
});
const obj = new MessageChannel().port1;
rejects(pool.runTask({ obj }));
});
test('postTask() resolves with a rejection when the handler rejects', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.runTask('Promise.reject(new Error("foo"))'), /foo/);
});
test('postTask() resolves with a rejection when the handler throws', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.runTask('throw new Error("foo")'), /foo/);
});
test('postTask() validates transferList', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.runTask('0', 42 as any),
/transferList argument must be an Array/);
rejects(pool.run('0', { transferList: 42 as any }),
/transferList argument must be an Array/);
});
test('postTask() validates filename', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.runTask('0', [], 42 as any),
/filename argument must be a string/);
rejects(pool.run('0', { filename: 42 as any }),
/filename argument must be a string/);
});
test('postTask() validates name', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.run('0', { name: 42 as any }),
/name argument must be a string/);
});
test('postTask() validates abortSignal', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.runTask('0', [], undefined, 42 as any),
/signal argument must be an object/);
rejects(pool.run('0', { signal: 42 as any }),
/signal argument must be an object/);
});
test('Piscina emits drain', async ({ ok, notOk }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
let drained = false;
let needsDrain = true;
pool.on('drain', () => {
drained = true;
needsDrain = pool.needsDrain;
});
await Promise.all([pool.run('123'), pool.run('123')]);
ok(drained);
notOk(needsDrain);
});
test('Piscina exposes/emits needsDrain to true when capacity is exceeded', async ({ ok }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
maxQueue: 3,
maxThreads: 1
});
let triggered = false;
let drained = false;
pool.once('drain', () => {
drained = true;
});
pool.once('needsDrain', () => {
triggered = true;
});
pool.run('123');
pool.run('123');
pool.run('123');
pool.run('123');
ok(pool.needsDrain);
ok(triggered);
ok(drained);
});
test('Piscina can use async loaded workers', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval-async.js')
});
equal(await pool.runTask('1'), 1);
});
test('Piscina can use async loaded esm workers', {}, async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/esm-async.mjs')
});
equal(await pool.runTask('1'), 1);
});
test('Piscina.run options is correct type', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
rejects(pool.run(42, 1 as any), /options must be an object/);
});
test('Piscina.maxThreads should return the max number of threads to be used (default)', ({ equal, plan }) => {
plan(1);
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const maxThreads = (cpus().length || 1) * 1.5;
equal(pool.maxThreads, maxThreads);
});
test('Piscina.minThreads should return the max number of threads to be used (custom)', ({ equal, plan }) => {
const maxThreads = 3;
const pool = new Piscina({
maxThreads,
filename: resolve(__dirname, 'fixtures/eval.js')
});
plan(1);
equal(pool.maxThreads, maxThreads);
});
test('Piscina.minThreads should return the max number of threads to be used (default)', ({ equal, plan }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const minThreads = Math.max((cpus().length || 1) / 2, 1);
plan(1);
equal(pool.minThreads, minThreads);
});
test('Piscina.minThreads should return the max number of threads to be used (custom)', ({ equal, plan }) => {
const minThreads = 2;
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads
});
plan(1);
equal(pool.minThreads, minThreads);
});

199
my-app/node_modules/piscina/test/simple-test.ts generated vendored Executable file
View file

@ -0,0 +1,199 @@
import Piscina from '..';
import { test } from 'tap';
import { version } from '../package.json';
import { pathToFileURL } from 'url';
import { resolve } from 'path';
import { EventEmitter } from 'events';
test('Piscina is exposed on export', async ({ equal }) => {
equal(Piscina.version, version);
});
test('Piscina is exposed on itself', async ({ equal }) => {
equal(Piscina.Piscina, Piscina);
});
test('Piscina.isWorkerThread has the correct value', async ({ equal }) => {
equal(Piscina.isWorkerThread, false);
});
test('Piscina.isWorkerThread has the correct value (worker)', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-isworkerthread.ts')
});
const result = await worker.runTask(null);
equal(result, 'done');
});
test('Piscina instance is an EventEmitter', async ({ ok }) => {
const piscina = new Piscina();
ok(piscina instanceof EventEmitter);
});
test('Piscina constructor options are correctly set', async ({ equal }) => {
const piscina = new Piscina({
minThreads: 10,
maxThreads: 20,
maxQueue: 30
});
equal(piscina.options.minThreads, 10);
equal(piscina.options.maxThreads, 20);
equal(piscina.options.maxQueue, 30);
});
test('trivial eval() handler works', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const result = await worker.runTask('42');
equal(result, 42);
});
test('async eval() handler works', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
const result = await worker.runTask('Promise.resolve(42)');
equal(result, 42);
});
test('filename can be provided while posting', async ({ equal }) => {
const worker = new Piscina();
const result = await worker.runTask(
'Promise.resolve(42)',
resolve(__dirname, 'fixtures/eval.js'));
equal(result, 42);
});
test('filename can be null when initially provided', async ({ equal }) => {
const worker = new Piscina({ filename: null });
const result = await worker.runTask(
'Promise.resolve(42)',
resolve(__dirname, 'fixtures/eval.js'));
equal(result, 42);
});
test('filename must be provided while posting', async ({ rejects }) => {
const worker = new Piscina();
rejects(worker.runTask('doesnt matter'),
/filename must be provided to run\(\) or in options object/);
});
test('passing env to workers works', async ({ same }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
env: { A: 'foo' }
});
const env = await pool.runTask('({...process.env})');
same(env, { A: 'foo' });
});
test('passing argv to workers works', async ({ same }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
argv: ['a', 'b', 'c']
});
const env = await pool.runTask('process.argv.slice(2)');
same(env, ['a', 'b', 'c']);
});
test('passing execArgv to workers works', async ({ same }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
execArgv: ['--no-warnings']
});
const env = await pool.runTask('process.execArgv');
same(env, ['--no-warnings']);
});
test('passing valid workerData works', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/simple-workerdata.ts'),
workerData: 'ABC'
});
equal(Piscina.workerData, undefined);
await pool.runTask(null);
});
test('passing invalid workerData does not work', async ({ throws }) => {
throws(() => new Piscina(({
filename: resolve(__dirname, 'fixtures/simple-workerdata.ts'),
workerData: {
hello () {}
}
}) as any), /could not be cloned./);
});
test('filename can be a file:// URL', async ({ equal }) => {
const worker = new Piscina({
filename: pathToFileURL(resolve(__dirname, 'fixtures/eval.js')).href
});
const result = await worker.runTask('42');
equal(result, 42);
});
test('filename can be a file:// URL to an ESM module', {}, async ({ equal }) => {
const worker = new Piscina({
filename: pathToFileURL(resolve(__dirname, 'fixtures/esm-export.mjs')).href
});
const result = await worker.runTask('42');
equal(result, 42);
});
test('duration and utilization calculations work', async ({ equal, ok }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
// Initial utilization is always 0
equal(worker.utilization, 0);
await Promise.all([
worker.runTask('42'),
worker.runTask('41'),
worker.runTask('40')
]);
// utilization is going to be some non-deterministic value
// between 0 and 1. It should not be zero at this point
// because tasks have run, but it should also never be 1
ok(worker.utilization > 0);
ok(worker.utilization < 1);
// Duration must be non-zero.
ok(worker.duration > 0);
});
test('run works also', async () => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
await worker.run(42);
});
test('named tasks work', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/multiple.js')
});
equal(await worker.run({}, { name: 'a' }), 'a');
equal(await worker.run({}, { name: 'b' }), 'b');
equal(await worker.run({}), 'a');
});
test('named tasks work', async ({ equal }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/multiple.js'),
name: 'b'
});
equal(await worker.run({}, { name: 'a' }), 'a');
equal(await worker.run({}, { name: 'b' }), 'b');
equal(await worker.run({}), 'b');
});

280
my-app/node_modules/piscina/test/task-queue.ts generated vendored Executable file
View file

@ -0,0 +1,280 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
import { Task, TaskQueue } from '../dist/src/common';
test('will put items into a task queue until they can run', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
minThreads: 2,
maxThreads: 3
});
equal(pool.threads.length, 2);
equal(pool.queueSize, 0);
const buffers = [
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4))
];
const results = [];
results.push(pool.runTask(buffers[0]));
equal(pool.threads.length, 2);
equal(pool.queueSize, 0);
results.push(pool.runTask(buffers[1]));
equal(pool.threads.length, 2);
equal(pool.queueSize, 0);
results.push(pool.runTask(buffers[2]));
equal(pool.threads.length, 3);
equal(pool.queueSize, 0);
results.push(pool.runTask(buffers[3]));
equal(pool.threads.length, 3);
equal(pool.queueSize, 1);
for (const buffer of buffers) {
Atomics.store(buffer, 0, 1);
Atomics.notify(buffer, 0, 1);
}
await results[0];
equal(pool.queueSize, 0);
await Promise.all(results);
});
test('will reject items over task queue limit', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 0,
maxThreads: 1,
maxQueue: 2
});
equal(pool.threads.length, 0);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 1);
rejects(pool.runTask('while (true) {}'), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 2);
rejects(pool.runTask('while (true) {}'), /Task queue is at limit/);
await pool.destroy();
});
test('will reject items when task queue is unavailable', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 0,
maxThreads: 1,
maxQueue: 0
});
equal(pool.threads.length, 0);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /No task queue available and all Workers are busy/);
await pool.destroy();
});
test('will reject items when task queue is unavailable (fixed thread count)', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 1,
maxThreads: 1,
maxQueue: 0
});
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask('while (true) {}'), /No task queue available and all Workers are busy/);
await pool.destroy();
});
test('tasks can share a Worker if requested (both tests blocking)', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
minThreads: 0,
maxThreads: 1,
maxQueue: 0,
concurrentTasksPerWorker: 2
});
equal(pool.threads.length, 0);
equal(pool.queueSize, 0);
rejects(pool.runTask(new Int32Array(new SharedArrayBuffer(4))));
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask(new Int32Array(new SharedArrayBuffer(4))));
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
await pool.destroy();
});
test('tasks can share a Worker if requested (one test finishes)', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
minThreads: 0,
maxThreads: 1,
maxQueue: 0,
concurrentTasksPerWorker: 2
});
const buffers = [
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4))
];
equal(pool.threads.length, 0);
equal(pool.queueSize, 0);
const firstTask = pool.runTask(buffers[0]);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
rejects(pool.runTask(
'new Promise((resolve) => setTimeout(resolve, 1000000))',
resolve(__dirname, 'fixtures/eval.js')), /Terminating worker thread/);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
Atomics.store(buffers[0], 0, 1);
Atomics.notify(buffers[0], 0, 1);
await firstTask;
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
await pool.destroy();
});
test('tasks can share a Worker if requested (both tests finish)', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/wait-for-notify.ts'),
minThreads: 1,
maxThreads: 1,
maxQueue: 0,
concurrentTasksPerWorker: 2
});
const buffers = [
new Int32Array(new SharedArrayBuffer(4)),
new Int32Array(new SharedArrayBuffer(4))
];
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
const firstTask = pool.runTask(buffers[0]);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
const secondTask = pool.runTask(buffers[1]);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
Atomics.store(buffers[0], 0, 1);
Atomics.store(buffers[1], 0, 1);
Atomics.notify(buffers[0], 0, 1);
Atomics.notify(buffers[1], 0, 1);
Atomics.wait(buffers[0], 0, 1);
Atomics.wait(buffers[1], 0, 1);
await firstTask;
equal(buffers[0][0], -1);
await secondTask;
equal(buffers[1][0], -1);
equal(pool.threads.length, 1);
equal(pool.queueSize, 0);
});
test('custom task queue works', async ({ equal, ok }) => {
let sizeCalled : boolean = false;
let shiftCalled : boolean = false;
let pushCalled : boolean = false;
class CustomTaskPool implements TaskQueue {
tasks: Task[] = [];
get size () : number {
sizeCalled = true;
return this.tasks.length;
}
shift () : Task | null {
shiftCalled = true;
return this.tasks.length > 0 ? this.tasks.shift() as Task : null;
}
push (task : Task) : void {
pushCalled = true;
this.tasks.push(task);
ok(Piscina.queueOptionsSymbol in task);
if ((task as any).task.a === 3) {
equal(task[Piscina.queueOptionsSymbol], null);
} else {
equal(task[Piscina.queueOptionsSymbol].option,
(task as any).task.a);
}
}
remove (task : Task) : void {
const index = this.tasks.indexOf(task);
this.tasks.splice(index, 1);
}
};
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
taskQueue: new CustomTaskPool(),
// Setting maxThreads low enough to ensure we queue
maxThreads: 1,
minThreads: 1
});
function makeTask (task, option) {
return { ...task, [Piscina.queueOptionsSymbol]: { option } };
}
const ret = await Promise.all([
pool.runTask(makeTask({ a: 1 }, 1)),
pool.runTask(makeTask({ a: 2 }, 2)),
pool.runTask({ a: 3 }) // No queueOptionsSymbol attached
]);
equal(ret[0].a, 1);
equal(ret[1].a, 2);
equal(ret[2].a, 3);
ok(sizeCalled);
ok(pushCalled);
ok(shiftCalled);
});

View file

@ -0,0 +1,29 @@
import Piscina from '../dist/src';
import { test } from 'tap';
import { resolve } from 'path';
function wait () {
return new Promise((resolve) => setTimeout(resolve, 1500));
}
test('transferable objects must be transferred', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/send-buffer-then-get-length.js'),
useAtomics: false
});
await pool.run({}, { name: 'send' });
await wait();
const after = await pool.run({}, { name: 'get' });
equal(after, 0);
});
test('objects that implement transferable must be transferred', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/send-transferrable-then-get-length.js'),
useAtomics: false
});
await pool.run({}, { name: 'send' });
await wait();
const after = await pool.run({}, { name: 'get' });
equal(after, 0);
});

34
my-app/node_modules/piscina/test/test-resourcelimits.ts generated vendored Executable file
View file

@ -0,0 +1,34 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
test('resourceLimits causes task to reject', async ({ equal, rejects }) => {
const worker = new Piscina({
filename: resolve(__dirname, 'fixtures/resource-limits.js'),
resourceLimits: {
maxOldGenerationSizeMb: 16,
maxYoungGenerationSizeMb: 4,
codeRangeSizeMb: 16
}
});
worker.on('error', () => {
// Ignore any additional errors that may occur.
// This may happen because when the Worker is
// killed a new worker is created that may hit
// the memory limits immediately. When that
// happens, there is no associated Promise to
// reject so we emit an error event instead.
// We don't care so much about that here. We
// could potentially avoid the issue by setting
// higher limits above but rather than try to
// guess at limits that may work consistently,
// let's just ignore the additional error for
// now.
});
const limits : any = worker.options.resourceLimits;
equal(limits.maxOldGenerationSizeMb, 16);
equal(limits.maxYoungGenerationSizeMb, 4);
equal(limits.codeRangeSizeMb, 16);
rejects(worker.runTask(null),
/Worker terminated due to reaching memory limit: JS heap out of memory/);
});

View file

@ -0,0 +1,86 @@
import Piscina from '..';
import { test } from 'tap';
import { resolve } from 'path';
import { once } from 'events';
test('uncaught exception resets Worker', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
await rejects(pool.runTask('throw new Error("not_caught")'), /not_caught/);
});
test('uncaught exception in immediate resets Worker', async ({ rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js')
});
await rejects(
pool.runTask(`
setImmediate(() => { throw new Error("not_caught") });
new Promise(() => {}) /* act as if we were doing some work */
`), /not_caught/);
});
test('uncaught exception in immediate after task yields error event', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
maxThreads: 1,
useAtomics: false
});
const errorEvent : Promise<Error[]> = once(pool, 'error');
const taskResult = pool.runTask(`
setTimeout(() => { throw new Error("not_caught") }, 500);
42
`);
equal(await taskResult, 42);
// Hack a bit to make sure we get the 'exit'/'error' events.
equal(pool.threads.length, 1);
pool.threads[0].ref();
// This is the main assertion here.
equal((await errorEvent)[0].message, 'not_caught');
});
test('exiting process resets worker', async ({ not, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 1
});
const originalThreadId = pool.threads[0].threadId;
await rejects(pool.runTask('process.exit(1);'), /worker exited with code: 1/);
const newThreadId = pool.threads[0].threadId;
not(originalThreadId, newThreadId);
});
test('exiting process in immediate after task errors next task and resets worker', async ({ equal, not, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval-async.js'),
minThreads: 1
});
const originalThreadId = pool.threads[0].threadId;
const taskResult = await pool.runTask(`
setTimeout(() => { process.exit(1); }, 50);
42
`);
equal(taskResult, 42);
await rejects(pool.runTask(`
'use strict';
const { promisify } = require('util');
const sleep = promisify(setTimeout);
async function _() {
await sleep(1000);
return 42
}
_();
`), /worker exited with code: 1/);
const secondThreadId = pool.threads[0].threadId;
not(originalThreadId, secondThreadId);
});

64
my-app/node_modules/piscina/test/thread-count.ts generated vendored Executable file
View file

@ -0,0 +1,64 @@
import Piscina from '..';
import { cpus } from 'os';
import { test } from 'tap';
import { resolve } from 'path';
test('will start with minThreads and max out at maxThreads', async ({ equal, rejects }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 2,
maxThreads: 4
});
equal(pool.threads.length, 2);
rejects(pool.runTask('while(true) {}'));
equal(pool.threads.length, 2);
rejects(pool.runTask('while(true) {}'));
equal(pool.threads.length, 2);
rejects(pool.runTask('while(true) {}'));
equal(pool.threads.length, 3);
rejects(pool.runTask('while(true) {}'));
equal(pool.threads.length, 4);
rejects(pool.runTask('while(true) {}'));
equal(pool.threads.length, 4);
await pool.destroy();
});
test('low maxThreads sets minThreads', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
maxThreads: 1
});
equal(pool.threads.length, 1);
equal(pool.options.minThreads, 1);
equal(pool.options.maxThreads, 1);
});
test('high minThreads sets maxThreads', {
skip: cpus().length > 8
}, async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 16
});
equal(pool.threads.length, 16);
equal(pool.options.minThreads, 16);
equal(pool.options.maxThreads, 16);
});
test('conflicting min/max threads is error', async ({ throws }) => {
throws(() => new Piscina({
minThreads: 16,
maxThreads: 8
}), /options.minThreads and options.maxThreads must not conflict/);
});
test('thread count should be 0 upon destruction', async ({ equal }) => {
const pool = new Piscina({
filename: resolve(__dirname, 'fixtures/eval.js'),
minThreads: 2,
maxThreads: 4
});
equal(pool.threads.length, 2);
await pool.destroy();
equal(pool.threads.length, 0);
});

10
my-app/node_modules/piscina/test/tsconfig.json generated vendored Executable file
View file

@ -0,0 +1,10 @@
{
"extends": "../tsconfig",
"compilerOptions": {
"strict": false,
"noImplicitAny": false
},
"include": [
"./**/*.ts"
]
}