Updated the files.
This commit is contained in:
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
233
my-app/node_modules/piscina/test/abort-task.ts
generated
vendored
Executable 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
43
my-app/node_modules/piscina/test/async-context.ts
generated
vendored
Executable 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
81
my-app/node_modules/piscina/test/atomics-optimization.ts
generated
vendored
Executable 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
17
my-app/node_modules/piscina/test/console-log.ts
generated
vendored
Executable 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
9
my-app/node_modules/piscina/test/fixtures/console-log.ts
generated
vendored
Executable 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
12
my-app/node_modules/piscina/test/fixtures/esm-async.mjs
generated
vendored
Executable 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
2
my-app/node_modules/piscina/test/fixtures/esm-export.mjs
generated
vendored
Executable 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
14
my-app/node_modules/piscina/test/fixtures/eval-async.js
generated
vendored
Executable 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
2
my-app/node_modules/piscina/test/fixtures/eval.js
generated
vendored
Executable 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
10
my-app/node_modules/piscina/test/fixtures/move.ts
generated
vendored
Executable 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
10
my-app/node_modules/piscina/test/fixtures/multiple.js
generated
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
function a () { return 'a'; }
|
||||
|
||||
function b () { return 'b'; }
|
||||
|
||||
a.a = a;
|
||||
a.b = b;
|
||||
|
||||
module.exports = a;
|
10
my-app/node_modules/piscina/test/fixtures/notify-then-sleep-or.ts
generated
vendored
Executable file
10
my-app/node_modules/piscina/test/fixtures/notify-then-sleep-or.ts
generated
vendored
Executable 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);
|
||||
};
|
5
my-app/node_modules/piscina/test/fixtures/notify-then-sleep.ts
generated
vendored
Executable file
5
my-app/node_modules/piscina/test/fixtures/notify-then-sleep.ts
generated
vendored
Executable file
|
@ -0,0 +1,5 @@
|
|||
module.exports = function (i32array) {
|
||||
Atomics.store(i32array, 0, 1);
|
||||
Atomics.notify(i32array, 0, Infinity);
|
||||
Atomics.wait(i32array, 0, 1);
|
||||
};
|
8
my-app/node_modules/piscina/test/fixtures/resource-limits.js
generated
vendored
Executable file
8
my-app/node_modules/piscina/test/fixtures/resource-limits.js
generated
vendored
Executable file
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = () => {
|
||||
const array = [];
|
||||
while (true) {
|
||||
array.push([array]);
|
||||
}
|
||||
};
|
18
my-app/node_modules/piscina/test/fixtures/send-buffer-then-get-length.js
generated
vendored
Executable file
18
my-app/node_modules/piscina/test/fixtures/send-buffer-then-get-length.js
generated
vendored
Executable 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;
|
||||
}
|
||||
};
|
38
my-app/node_modules/piscina/test/fixtures/send-transferrable-then-get-length.js
generated
vendored
Executable file
38
my-app/node_modules/piscina/test/fixtures/send-transferrable-then-get-length.js
generated
vendored
Executable 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;
|
||||
}
|
||||
};
|
6
my-app/node_modules/piscina/test/fixtures/simple-isworkerthread.ts
generated
vendored
Executable file
6
my-app/node_modules/piscina/test/fixtures/simple-isworkerthread.ts
generated
vendored
Executable file
|
@ -0,0 +1,6 @@
|
|||
import Piscina from '../..';
|
||||
import assert from 'assert';
|
||||
|
||||
assert.strictEqual(Piscina.isWorkerThread, true);
|
||||
|
||||
export default function () { return 'done'; }
|
6
my-app/node_modules/piscina/test/fixtures/simple-workerdata.ts
generated
vendored
Executable file
6
my-app/node_modules/piscina/test/fixtures/simple-workerdata.ts
generated
vendored
Executable 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
12
my-app/node_modules/piscina/test/fixtures/sleep.js
generated
vendored
Executable 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;
|
||||
};
|
5
my-app/node_modules/piscina/test/fixtures/wait-for-notify.ts
generated
vendored
Executable file
5
my-app/node_modules/piscina/test/fixtures/wait-for-notify.ts
generated
vendored
Executable 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
11
my-app/node_modules/piscina/test/fixtures/wait-for-others.ts
generated
vendored
Executable 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
31
my-app/node_modules/piscina/test/histogram.ts
generated
vendored
Executable 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
44
my-app/node_modules/piscina/test/idle-timeout.ts
generated
vendored
Executable 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
21
my-app/node_modules/piscina/test/load-with-esm.ts
generated
vendored
Executable 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
19
my-app/node_modules/piscina/test/messages.ts
generated
vendored
Executable 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
107
my-app/node_modules/piscina/test/move-test.ts
generated
vendored
Executable 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
31
my-app/node_modules/piscina/test/nice.ts
generated
vendored
Executable 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
129
my-app/node_modules/piscina/test/option-validation.ts
generated
vendored
Executable 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
99
my-app/node_modules/piscina/test/pool-close.ts
generated
vendored
Executable 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
11
my-app/node_modules/piscina/test/pool-destroy.ts
generated
vendored
Executable 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
207
my-app/node_modules/piscina/test/post-task.ts
generated
vendored
Executable 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
199
my-app/node_modules/piscina/test/simple-test.ts
generated
vendored
Executable 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('doesn’t 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
280
my-app/node_modules/piscina/test/task-queue.ts
generated
vendored
Executable 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);
|
||||
});
|
29
my-app/node_modules/piscina/test/test-is-buffer-transferred.ts
generated
vendored
Executable file
29
my-app/node_modules/piscina/test/test-is-buffer-transferred.ts
generated
vendored
Executable 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
34
my-app/node_modules/piscina/test/test-resourcelimits.ts
generated
vendored
Executable 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/);
|
||||
});
|
86
my-app/node_modules/piscina/test/test-uncaught-exception-from-handler.ts
generated
vendored
Executable file
86
my-app/node_modules/piscina/test/test-uncaught-exception-from-handler.ts
generated
vendored
Executable 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
64
my-app/node_modules/piscina/test/thread-count.ts
generated
vendored
Executable 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
10
my-app/node_modules/piscina/test/tsconfig.json
generated
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../tsconfig",
|
||||
"compilerOptions": {
|
||||
"strict": false,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue