## [Unreleased]
+### Added
+
+- Add safe helper `availableParallelism` to help sizing the pool.
+
### Fixed
- Ensure message handler is only registered in worker.
Node pool contains two [worker-threads](https://nodejs.org/api/worker_threads.html#worker_threads_worker_threads)/[cluster worker](https://nodejs.org/api/cluster.html#cluster_class_worker) pool implementations, you don't have to deal with worker-threads/cluster worker complexity.
The first implementation is a static worker pool, with a defined number of workers that are started at creation time and will be reused.
The second implementation is a dynamic worker pool with a number of worker started at creation time (these workers will be always active and reused) and other workers created when the load will increase (with an upper limit, these workers will be reused when active), the new created workers will be stopped after a configurable period of inactivity.
-You have to implement your worker extending the ThreadWorker or ClusterWorker class.
+You have to implement your worker by extending the ThreadWorker or ClusterWorker class.
## Installation
```js
'use strict'
-const { DynamicThreadPool, FixedThreadPool, PoolEvents } = require('poolifier')
+const { DynamicThreadPool, FixedThreadPool, PoolEvents, availableParallelism } = require('poolifier')
// a fixed worker-threads pool
-const pool = new FixedThreadPool(15, './yourWorker.js', {
+const pool = new FixedThreadPool(availableParallelism(), './yourWorker.js', {
errorHandler: e => console.error(e),
onlineHandler: () => console.info('worker is online')
})
pool.emitter.on(PoolEvents.busy, () => console.info('Pool is busy'))
// or a dynamic worker-threads pool
-const pool = new DynamicThreadPool(10, 100, './yourWorker.js', {
+const pool = new DynamicThreadPool(availableParallelism() / 2, availableParallelism(), './yourWorker.js', {
errorHandler: e => console.error(e),
onlineHandler: () => console.info('worker is online')
})
## Poolifier vs other pools benchmark
To compare poolifier pools performance vs other pools performance we chose to use [hyperfine](https://github.com/sharkdp/hyperfine).
-We chose to use this tool because it allows to run isolated Node.js processes so each pool does not impact each other.
-External pools with which we compared the poolifier results:
+We chose to use this tool because it allows to run isolated Node.js processes so each pool does not impact each other.
-- [piscina](https://github.com/piscinajs/piscina)
-- [tinypool](https://github.com/tinylibs/tinypool)
-- [workerpool](https://github.com/josdejong/workerpool)
-- [worker-nodes](https://github.com/allegro/node-worker-nodes)
-- [node-worker-threads-pool](https://github.com/SUCHMOKUO/node-worker-threads-pool)
+- External pools with which we compare the poolifier results:
-Those are our results:
+ - [piscina](https://github.com/piscinajs/piscina)
+ - [tinypool](https://github.com/tinylibs/tinypool)
+ - [workerpool](https://github.com/josdejong/workerpool)
+ - [worker-nodes](https://github.com/allegro/node-worker-nodes)
+ - [node-worker-threads-pool](https://github.com/SUCHMOKUO/node-worker-threads-pool)
-- CPU Intensive task with 100k operations submitted to each pool [BENCH-100000.md](./versus-external-pools/BENCH-100000.md).
+ Those are our results:
-> :warning: **We would need funds to run our benchmarks more often and on Cloud VMs, please consider to sponsor this project**
+ - CPU Intensive task with 100k operations submitted to each pool [BENCH-100000.md](./versus-external-pools/BENCH-100000.md).
+
+- External pools with which we used to compare the poolifier results:
-External pools with which we used to compare the poolifier results:
+ <!-- - [node-worker-threads-pool](https://github.com/SUCHMOKUO/node-worker-threads-pool): removed because it does not support dynamic modules import or import outside the worker function. The worker function is expected to be self-contained, which makes it difficult to use in real world application without ugly hacks. -->
-<!-- - [node-worker-threads-pool](https://github.com/SUCHMOKUO/node-worker-threads-pool): removed because it does not support dynamic modules import or import outside the worker function. The worker function is expected to be self-contained, which makes it difficult to use in real world application without ugly hacks. -->
+ - [worker-threads-pool](https://github.com/watson/worker-threads-pool): removed because unmaintained since more than 4 years.
+ - [threadwork](https://github.com/kevlened/threadwork): removed because unmaintained since more than 3 years.
+ - [microjob](https://github.com/wilk/microjob): removed because unmaintained since more than 5 years.
+ - [threads.js](https://github.com/andywer/threads.js/): removed because not a threads pool.
-- [worker-threads-pool](https://github.com/watson/worker-threads-pool): removed because unmaintained since more than 4 years.
-- [threadwork](https://github.com/kevlened/threadwork): removed because unmaintained since more than 3 years.
-- [microjob](https://github.com/wilk/microjob): removed because unmaintained since more than 5 years.
-- [threads.js](https://github.com/andywer/threads.js/): removed because not a threads pool.
+> :warning: **We would need funds to run our benchmarks more often and on Cloud VMs, please consider to sponsor this project**
### Internal
-const { DynamicThreadPool, PoolEvents } = require('poolifier')
-let resolved = 0
+const {
+ DynamicThreadPool,
+ PoolEvents,
+ availableParallelism
+} = require('poolifier')
+
+const pool = new DynamicThreadPool(
+ availableParallelism() / 2,
+ availableParallelism(),
+ './yourWorker.js',
+ {
+ errorHandler: e => console.error(e),
+ onlineHandler: () => console.info('worker is online')
+ }
+)
let poolFull = 0
let poolBusy = 0
-const pool = new DynamicThreadPool(10, 20, './yourWorker.js', {
- errorHandler: e => console.error(e),
- onlineHandler: () => console.info('worker is online')
-})
pool.emitter.on(PoolEvents.full, () => poolFull++)
pool.emitter.on(PoolEvents.busy, () => poolBusy++)
+let resolved = 0
const start = performance.now()
const iterations = 1000
for (let i = 1; i <= iterations; i++) {
-const { FixedThreadPool, PoolEvents } = require('poolifier')
-let resolved = 0
-let poolBusy = 0
-const pool = new FixedThreadPool(15, './yourWorker.js', {
+const {
+ FixedThreadPool,
+ PoolEvents,
+ availableParallelism
+} = require('poolifier')
+
+const pool = new FixedThreadPool(availableParallelism(), './yourWorker.js', {
errorHandler: e => console.error(e),
onlineHandler: () => console.info('worker is online')
})
+let poolBusy = 0
pool.emitter.on(PoolEvents.busy, () => poolBusy++)
+let resolved = 0
const start = performance.now()
const iterations = 1000
for (let i = 1; i <= iterations; i++) {
-const { FixedThreadPool } = require('poolifier')
-const pool = new FixedThreadPool(15, './multiFunctionWorker.js', {
- errorHandler: e => console.error(e),
- onlineHandler: () => console.info('worker is online')
-})
+const { FixedThreadPool, availableParallelism } = require('poolifier')
+
+const pool = new FixedThreadPool(
+ availableParallelism(),
+ './multiFunctionWorker.js',
+ {
+ errorHandler: e => console.error(e),
+ onlineHandler: () => console.info('worker is online')
+ }
+)
pool
.execute({ text: 'hello' }, 'fn0')
import { join } from 'path'
import type { MyData, MyResponse } from './worker'
-import { DynamicThreadPool, FixedThreadPool } from 'poolifier'
+import {
+ DynamicThreadPool,
+ FixedThreadPool,
+ availableParallelism
+} from 'poolifier'
export const fixedPool = new FixedThreadPool<MyData, Promise<MyResponse>>(
- 8,
+ availableParallelism(),
join(__dirname, 'worker.js'),
{
errorHandler: (e: Error) => {
)
export const dynamicPool = new DynamicThreadPool<MyData, Promise<MyResponse>>(
- 2,
- 8,
+ availableParallelism() / 2,
+ availableParallelism(),
join(__dirname, 'worker.js'),
{
errorHandler: (e: Error) => {
} from './utility-types'
export type { CircularArray } from './circular-array'
export type { Queue } from './queue'
+export { availableParallelism } from './utils'
+import os from 'node:os'
import type {
MeasurementStatisticsRequirements,
WorkerChoiceStrategyOptions
median: false
}
+/**
+ * Safe helper to get the host OS optimized maximum pool size.
+ */
+export const availableParallelism = (): number => {
+ let availableParallelism = 1
+ try {
+ availableParallelism = os.availableParallelism()
+ } catch {
+ const cpus = os.cpus()
+ if (Array.isArray(cpus) && cpus.length > 0) {
+ availableParallelism = cpus.length
+ }
+ }
+ return availableParallelism
+}
+
/**
* Compute the median of the given data set.
*
const { expect } = require('expect')
-const { isPlainObject, median } = require('../lib/utils')
+const { isPlainObject, median, availableParallelism } = require('../lib/utils')
const {
isKillBehavior,
KillBehaviors
expect(isKillBehavior(KillBehaviors.HARD, null)).toBe(false)
expect(isKillBehavior(KillBehaviors.SOFT, 'unknown')).toBe(false)
})
+
+ it('Verify availableParallelism() behavior', () => {
+ expect(typeof availableParallelism() === 'number').toBe(true)
+ })
})