Comparing version 1.0.0-pre to 1.0.0
{ | ||
"name": "piscina", | ||
"version": "1.0.0-pre", | ||
"version": "1.0.0", | ||
"description": "A fast, efficient Node.js Worker Thread Pool implementation", | ||
"main": "./build/src/index.js", | ||
"main": "./dist/src/index.js", | ||
"exports": "./dist/src/index.js", | ||
"types": "./dist/src/index.d.ts", | ||
"scripts": { | ||
"build": "tsc", | ||
"lint": "standardx **/*.ts | snazzy", | ||
"lint": "standardx \"**/*.ts\" | snazzy", | ||
"test": "npm run lint && npm run build && npm run test-only", | ||
"test-only": "TS_NODE_PROJECT=test/tsconfig.json tap --ts --no-browser --coverage-report=html --100 test/*.ts", | ||
"test-only": "tap", | ||
"prepack": "npm run build" | ||
@@ -23,5 +25,6 @@ }, | ||
], | ||
"author": "James M Snell <piscina@jasnell.me>", | ||
"author": "James M Snell <jasnell@gmail.com>", | ||
"contributors": [ | ||
"Anna Henningsen <anna@addaleax.net>" | ||
"Anna Henningsen <anna@addaleax.net>", | ||
"Matteo Collina <matteo.collina@gmail.com>" | ||
], | ||
@@ -33,2 +36,3 @@ "license": "MIT", | ||
"@typescript-eslint/parser": "^2.28.0", | ||
"abort-controller": "^3.0.0", | ||
"snazzy": "^8.0.0", | ||
@@ -39,2 +43,6 @@ "standardx": "^5.0.0", | ||
}, | ||
"dependencies": { | ||
"hdr-histogram-js": "^1.2.0", | ||
"hdr-histogram-percentiles-obj": "^2.0.1" | ||
}, | ||
"eslintConfig": { | ||
@@ -41,0 +49,0 @@ "rules": { |
215
README.md
@@ -9,3 +9,9 @@ # piscina - the node.js worker pool | ||
* ✔ Proper async tracking integration | ||
* ✔ Tracking statistics for run and wait times | ||
* ✔ Cancelation Support | ||
For Node.js 12.x and higher. | ||
[MIT Licensed][]. | ||
## Piscina API | ||
@@ -21,3 +27,3 @@ | ||
const piscina = new Piscina({ | ||
filename: path.resolve(__dirname, 'worker.js'); | ||
filename: path.resolve(__dirname, 'worker.js') | ||
}); | ||
@@ -39,4 +45,81 @@ | ||
The worker may also be an async function or may return a Promise: | ||
```js | ||
const { promisify } = require('util'); | ||
const sleep = promisify(setTimeout); | ||
module.exports = async ({ a, b } => { | ||
// Fake some async activity | ||
await sleep(100); | ||
return a + b; | ||
}) | ||
``` | ||
### Cancelable Tasks | ||
Submitted tasks may be canceled using either an `AbortController` or | ||
an `EventEmitter`: | ||
```js | ||
'use strict'; | ||
const Piscina = require('piscina'); | ||
const { AbortController } = require('abort-controller'); | ||
const { resolve } = require('path'); | ||
const piscina = new Piscina({ | ||
filename: resolve(__dirname, 'worker.js') | ||
}); | ||
(async function() { | ||
const abortController = new AbortController(); | ||
try { | ||
const task = piscina.runTask({ a: 4, b: 6 }, abortController.signal); | ||
abortController.abort(); | ||
await task; | ||
} catch (err) { | ||
console.log('The task was cancelled'); | ||
} | ||
})(); | ||
``` | ||
To use `AbortController`, you will need to `npm i abort-controller` | ||
(or `yarn add abort-controller`). | ||
Alternatively, any `EventEmitter` that emits an `'abort'` event | ||
may be used as an abort controller: | ||
```js | ||
'use strict'; | ||
const Piscina = require('piscina'); | ||
const EventEmitter = require('events'); | ||
const { resolve } = require('path'); | ||
const piscina = new Piscina({ | ||
filename: resolve(__dirname, 'worker.js') | ||
}); | ||
(async function() { | ||
const ee = new EventEmitter(); | ||
try { | ||
const task = piscina.runTask({ a: 4, b: 6 }, ee); | ||
ee.emit('abort'); | ||
await task; | ||
} catch (err) { | ||
console.log('The task was cancelled'); | ||
} | ||
})(); | ||
``` | ||
## Class: `Piscina` | ||
Piscina works by creating a pool of Node.js Worker Threads to which | ||
one or more tasks may be dispatched. Each worker thread executes a | ||
single exported function defined in a separate file. Whenever a | ||
task is dispatched to a worker, the worker invokes the exported | ||
function and reports the return value back to Piscina when the | ||
function completes. | ||
This class extends [`EventEmitter`][] from Node.js. | ||
@@ -71,3 +154,3 @@ | ||
### Method: `runTask(task[, transferList][, filename])` | ||
### Method: `runTask(task[, transferList][, filename][, abortSignal])` | ||
@@ -84,2 +167,8 @@ Schedules a task to be run on a Worker thread. | ||
this is mandatory. | ||
* `abortSignal`: An [`AbortSignal`][] instance. If passed, this can be used to | ||
cancel a task. If the task is already running, the corresponding `Worker` | ||
thread will be stopped. | ||
(More generally, any `EventEmitter` or `EventTarget` that emits `'abort'` | ||
events can be passed here.) Abortable tasks cannot share threads regardless | ||
of the `concurrentTasksPerWorker` options. | ||
@@ -89,2 +178,4 @@ This returns a `Promise` for the return value of the (async) function call | ||
an error, the returned `Promise` will be rejected with that error. | ||
If the task is aborted, the returned `Promise` is rejected with an error | ||
as well. | ||
@@ -108,2 +199,6 @@ ### Method: `destroy()` | ||
### Property: `completed` (readonly) | ||
The current number of completed tasks. | ||
### Property: `options` (readonly) | ||
@@ -114,2 +209,43 @@ | ||
### Property: `runTime` (readonly) | ||
A histogram summary object summarizing the collected run times of completed | ||
tasks. All values are expressed in milliseconds. | ||
* `runTime.average` {`number`} The average run time of all tasks | ||
* `runTime.mean` {`number`} The mean run time of all tasks | ||
* `runTime.stddev` {`number`} The standard deviation of collected run times | ||
* `runTime.min` {`number`} The fastest recorded run time | ||
* `runTime.max` {`number`} The slowest recorded run time | ||
All properties following the pattern `p{N}` where N is a number (e.g. `p1`, `p99`) | ||
represent the percentile distributions of run time observations. For example, | ||
`p99` is the 99th percentile indicating that 99% of the observed run times were | ||
faster or equal to the given value. | ||
```js | ||
{ | ||
average: 1880.25, | ||
mean: 1880.25, | ||
stddev: 1.93, | ||
min: 1877, | ||
max: 1882.0190887451172, | ||
p0_001: 1877, | ||
p0_01: 1877, | ||
p0_1: 1877, | ||
p1: 1877, | ||
p2_5: 1877, | ||
p10: 1877, | ||
p25: 1877, | ||
p50: 1881, | ||
p75: 1881, | ||
p90: 1882, | ||
p97_5: 1882, | ||
p99: 1882, | ||
p99_9: 1882, | ||
p99_99: 1882, | ||
p99_999: 1882 | ||
} | ||
``` | ||
### Property: `threads` (readonly) | ||
@@ -123,2 +259,43 @@ | ||
### Property: `waitTime` (readonly) | ||
A histogram summary object summarizing the collected times tasks spent | ||
waiting in the queue. All values are expressed in milliseconds. | ||
* `waitTime.average` {`number`} The average wait time of all tasks | ||
* `waitTime.mean` {`number`} The mean wait time of all tasks | ||
* `waitTime.stddev` {`number`} The standard deviation of collected wait times | ||
* `waitTime.min` {`number`} The fastest recorded wait time | ||
* `waitTime.max` {`number`} The longest recorded wait time | ||
All properties following the pattern `p{N}` where N is a number (e.g. `p1`, `p99`) | ||
represent the percentile distributions of wait time observations. For example, | ||
`p99` is the 99th percentile indicating that 99% of the observed wait times were | ||
faster or equal to the given value. | ||
```js | ||
{ | ||
average: 1880.25, | ||
mean: 1880.25, | ||
stddev: 1.93, | ||
min: 1877, | ||
max: 1882.0190887451172, | ||
p0_001: 1877, | ||
p0_01: 1877, | ||
p0_1: 1877, | ||
p1: 1877, | ||
p2_5: 1877, | ||
p10: 1877, | ||
p25: 1877, | ||
p50: 1881, | ||
p75: 1881, | ||
p90: 1882, | ||
p97_5: 1882, | ||
p99: 1882, | ||
p99_9: 1882, | ||
p99_99: 1882, | ||
p99_999: 1882 | ||
} | ||
``` | ||
### Static property: `isWorkerThread` (readonly) | ||
@@ -132,9 +309,41 @@ | ||
## Current Limitations (Things we're working on / would love help with) | ||
* ESM Support | ||
* Exposing piscina as an ESM | ||
* Allowing Workers to be ESMs | ||
* Improved Documentation | ||
* More examples | ||
* Benchmarks | ||
## Performance Notes | ||
Workers are generally optimized for offloading synchronous, | ||
compute-intensive operations off the main Node.js event loop thread. | ||
While it is possible to perform asynchronous operations and I/O | ||
within a Worker, the performance advantages of doing so will be | ||
minimal. | ||
Specifically, it is worth noting that asynchronous operations | ||
within Node.js, including I/O such as file system operations | ||
or CPU-bound tasks such as crypto operations or compression | ||
algorithms, are already performed in parallel by Node.js and | ||
libuv on a per-process level. This means that there will be | ||
little performance impact on moving such async operations into | ||
a Piscina worker (see examples/scrypt for example). | ||
## The Team | ||
* James M Snell <piscina@jasnell.me> | ||
* James M Snell <jasnell@gmail.com> | ||
* Anna Henningsen <anna@addaleax.net> | ||
* Matteo Collina <matteo.collina@gmail.com> | ||
## Acknowledgements | ||
Piscina development is sponsored by [NearForm Research][]. | ||
[`Atomics`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics | ||
[`EventEmitter`]: https://nodejs.org/api/events.html | ||
[`postMessage`]: https://nodejs.org/api/worker_threads.html#worker_threads_port_postmessage_value_transferlist | ||
[MIT Licensed]: LICENSE.md | ||
[NearForm Research]: https://www.nearform.com/research/ |
{ | ||
"compilerOptions": { | ||
"target": "es2016", | ||
"target": "es2018", | ||
"module": "commonjs", | ||
"lib": ["es6"], | ||
"outDir": "build", | ||
"moduleResolution": "node", | ||
"lib": ["es2018"], | ||
"outDir": "dist", | ||
"rootDir": ".", | ||
@@ -8,0 +9,0 @@ "declaration": true, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
126413
56
2328
1
340
2
8
8
1
+ Addedhdr-histogram-js@^1.2.0
+ Addedbase64-js@1.5.1(transitive)
+ Addedhdr-histogram-js@1.2.0(transitive)
+ Addedhdr-histogram-percentiles-obj@2.0.1(transitive)
+ Addedpako@1.0.11(transitive)