Merge branch 'master' into worker-info
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 1 Jul 2023 10:33:13 +0000 (12:33 +0200)
committerGitHub <noreply@github.com>
Sat, 1 Jul 2023 10:33:13 +0000 (12:33 +0200)
CHANGELOG.md
README.md
benchmarks/README.md
examples/dynamicExample.js
examples/fixedExample.js
examples/multiFunctionExample.js
examples/typescript/pool.ts
src/index.ts
src/utils.ts
tests/utils.test.js

index 9a6fb3863e576b2eb229a6fbc4232598c9d6318b..91cf115fa07e0e8ab8442a204ea73545d74ac727 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Add safe helper `availableParallelism` to help sizing the pool.
+
 ### Fixed
 
 - Ensure message handler is only registered in worker.
index d16fe9469287cde778ae05a224700a6b8330986a..53ea832dffd985bd05f8d714370be03c994e71f7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ Please consult our [general guidelines](#general-guidance).
 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
 
@@ -114,10 +114,10 @@ Instantiate your pool based on your needs :
 
 ```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')
 })
@@ -125,7 +125,7 @@ const pool = new FixedThreadPool(15, './yourWorker.js', {
 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')
 })
index 400bb152802bbfb6598afdb786df7bc9614d39b3..6558913e226555d4126e1b8ae584c879c00ade81 100644 (file)
@@ -10,29 +10,30 @@ The [versus-external-pools](./versus-external-pools) folder contains benchmarks
 ## 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
 
index 59d992a67e202387d2b3dec6e6b6538e326a9985..6af045601025e3edac7c95a0b670b85b9c37f1f3 100644 (file)
@@ -1,14 +1,24 @@
-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++) {
index 478c37eb056229360dbdbabcf61ab13e7f058e74..2346f7b4f0b68b207ced0df9ffd86f125e36a1e5 100644 (file)
@@ -1,12 +1,17 @@
-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++) {
index 055f143fdafae8ee1eb82e65f161d0f69f6b2e4e..829ba93d4c59e852f872a47c6d5dbaa9dc4c5597 100644 (file)
@@ -1,8 +1,13 @@
-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')
index 869e62a8bf718020c155ae221b85921735a61d74..b6bb4da3427a93132842e5953cd6fa858d52408d 100644 (file)
@@ -1,9 +1,13 @@
 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) => {
@@ -16,8 +20,8 @@ export const fixedPool = new FixedThreadPool<MyData, Promise<MyResponse>>(
 )
 
 export const dynamicPool = new DynamicThreadPool<MyData, Promise<MyResponse>>(
-  2,
-  8,
+  availableParallelism() / 2,
+  availableParallelism(),
   join(__dirname, 'worker.js'),
   {
     errorHandler: (e: Error) => {
index 367c205c977582f14ea68b4f8d8776eff373581f..9bd8bbf9a7d99a755bf402e678011fed0e762314 100644 (file)
@@ -65,3 +65,4 @@ export type {
 } from './utility-types'
 export type { CircularArray } from './circular-array'
 export type { Queue } from './queue'
+export { availableParallelism } from './utils'
index c7efbc22638c59b55bd5e7508f3e19877c3c12a1..a6f5fbcc5b729b98f5dec1da1de8178eff830d40 100644 (file)
@@ -1,3 +1,4 @@
+import os from 'node:os'
 import type {
   MeasurementStatisticsRequirements,
   WorkerChoiceStrategyOptions
@@ -30,6 +31,22 @@ export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS: MeasurementStatisticsR
     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.
  *
index b1a91776a61ac63abefb10eea11ee11f19c1b29d..f62d49c68af0a9a3a187cbf06d7899b7685cc829 100644 (file)
@@ -1,5 +1,5 @@
 const { expect } = require('expect')
-const { isPlainObject, median } = require('../lib/utils')
+const { isPlainObject, median, availableParallelism } = require('../lib/utils')
 const {
   isKillBehavior,
   KillBehaviors
@@ -60,4 +60,8 @@ describe('Utils test suite', () => {
     expect(isKillBehavior(KillBehaviors.HARD, null)).toBe(false)
     expect(isKillBehavior(KillBehaviors.SOFT, 'unknown')).toBe(false)
   })
+
+  it('Verify availableParallelism() behavior', () => {
+    expect(typeof availableParallelism() === 'number').toBe(true)
+  })
 })