matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
node: ['22.x', '24.x', 'latest']
+ include:
+ - os: ubuntu-latest
+ node: '22.18.0'
+ - os: ubuntu-latest
+ node: '24.3.0'
name: Node.js ${{ matrix.node }} on ${{ matrix.os }}
## Node.js versions
-Node.js versions >= 22.x.x are supported.
+Supported Node.js versions are **22.18.0 or later in the 22.x line**, or **24.3.0 and later**.
## [API](./docs/api.md)
return new DynamicClusterPool(
Math.floor(poolSize / 2),
poolSize,
- './benchmarks/internal/cluster-worker.cjs',
+ './benchmarks/internal/cluster-worker.mjs',
poolOptions
)
case WorkerTypes.thread:
case WorkerTypes.cluster:
return new FixedClusterPool(
poolSize,
- './benchmarks/internal/cluster-worker.cjs',
+ './benchmarks/internal/cluster-worker.mjs',
poolOptions
)
case WorkerTypes.thread:
-const { isPrimary } = require('node:cluster')
+import { isPrimary } from 'node:cluster'
-const { ClusterWorker } = require('../../lib/index.cjs')
-const { TaskFunctions } = require('../benchmarks-types.cjs')
-const { executeTaskFunction } = require('../benchmarks-utils.cjs')
+import { ClusterWorker } from '../../lib/index.mjs'
+import { TaskFunctions } from '../benchmarks-types.cjs'
+import { executeTaskFunction } from '../benchmarks-utils.mjs'
const taskFunction = data => {
data = data || {}
return res
}
-module.exports = new ClusterWorker(taskFunction)
+export default new ClusterWorker(taskFunction)
languageOptions: {
parserOptions: {
projectService: true,
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
tsconfigRootDir: import.meta.dirname,
},
},
"description": "Express cluster pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"benchmark:echo": "autocannon -c 100 -d 30 -p 10 -m POST -H Content-Type=application/json -b '{\"key1\":\"value1\", \"key2\":\"value2\"}' http://localhost:8080/api/echo",
"benchmark:factorial": "autocannon -c 100 -d 30 -p 10 http://localhost:8080/api/factorial/50000"
export default defineConfig({
external: ['express', /^node:*/, 'poolifier'],
input: ['./src/main.ts', './src/worker.ts'],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
import type { WorkerData, WorkerResponse } from './types.js'
-const workerFile = join(
- dirname(fileURLToPath(import.meta.url)),
- `worker${extname(fileURLToPath(import.meta.url))}`
-)
+const workerFile = join(dirname(fileURLToPath(import.meta.url)), 'worker.js')
const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
availableParallelism(),
"description": "Express hybrid pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"benchmark:echo": "autocannon -c 100 -d 30 -p 10 -m POST -H Content-Type=application/json -b '{\"key1\":\"value1\", \"key2\":\"value2\"}' http://localhost:8080/api/echo",
"benchmark:factorial": "autocannon -c 100 -d 30 -p 10 http://localhost:8080/api/factorial/50000"
'./src/express-worker.ts',
'./src/request-handler-worker.ts',
],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
const expressWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `express-worker${extname(fileURLToPath(import.meta.url))}`
+ 'express-worker.js'
)
const requestHandlerWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `request-handler-worker${extname(fileURLToPath(import.meta.url))}`
+ 'request-handler-worker.js'
)
const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
"description": "Fastify cluster pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"benchmark:echo": "autocannon -c 100 -d 30 -p 10 -m POST -H Content-Type=application/json -b '{\"key1\":\"value1\", \"key2\":\"value2\"}' http://localhost:8080/api/echo",
"benchmark:factorial": "autocannon -c 100 -d 30 -p 10 http://localhost:8080/api/factorial/50000"
export default defineConfig({
external: ['fastify', /^node:*/, 'poolifier'],
input: ['./src/main.ts', './src/worker.ts'],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
import type { WorkerData, WorkerResponse } from './types.js'
-const workerFile = join(
- dirname(fileURLToPath(import.meta.url)),
- `worker${extname(fileURLToPath(import.meta.url))}`
-)
+const workerFile = join(dirname(fileURLToPath(import.meta.url)), 'worker.js')
const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
availableParallelism(),
"description": "Fastify hybrid pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"benchmark:echo": "autocannon -c 100 -d 30 -p 10 -m POST -H Content-Type=application/json -b '{\"key1\":\"value1\", \"key2\":\"value2\"}' http://localhost:8080/api/echo",
"benchmark:factorial": "autocannon -c 100 -d 30 -p 10 http://localhost:8080/api/factorial/50000"
'./src/fastify-worker.ts',
'./src/request-handler-worker.ts',
],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
const fastifyWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `fastify-worker${extname(fileURLToPath(import.meta.url))}`
+ 'fastify-worker.js'
)
const requestHandlerWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `request-handler-worker${extname(fileURLToPath(import.meta.url))}`
+ 'request-handler-worker.js'
)
const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
"description": "ws cluster pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"test:requests": "node requests.js"
},
export default defineConfig({
external: [/^node:*/, 'poolifier', 'ws'],
input: ['./src/main.ts', './src/worker.ts'],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
import type { WorkerData, WorkerResponse } from './types.js'
-const workerFile = join(
- dirname(fileURLToPath(import.meta.url)),
- `worker${extname(fileURLToPath(import.meta.url))}`
-)
+const workerFile = join(dirname(fileURLToPath(import.meta.url)), 'worker.js')
const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
availableParallelism(),
"description": "ws hybrid pool",
"main": "dist/main.js",
"type": "module",
+ "engines": {
+ "node": "^22.18.0 || >=24.3.0"
+ },
"packageManager": "pnpm@12.11.2",
"scripts": {
"build": "rollup --config --configPlugin typescript",
- "start": "node --enable-source-maps dist/main.cjs",
- "start:esm": "node --enable-source-maps dist/main.js",
+ "start": "node --enable-source-maps dist/main.js",
"test": "echo \"Error: no test specified\" && exit 1",
"test:requests": "node requests.js"
},
'./src/websocket-server-worker.ts',
'./src/request-handler-worker.ts',
],
- output: [
- {
- chunkFileNames: '[name]-[hash].cjs',
- dir: './dist',
- entryFileNames: '[name].cjs',
- format: 'cjs',
- sourcemap: true,
- },
- {
- dir: './dist',
- format: 'esm',
- sourcemap: true,
- },
- ],
+ output: {
+ dir: './dist',
+ format: 'esm',
+ sourcemap: true,
+ },
plugins: [
typescript({
compilerOptions: {
-import { dirname, extname, join } from 'node:path'
+import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { availableParallelism, FixedClusterPool } from 'poolifier'
const webSocketServerWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `websocket-server-worker${extname(fileURLToPath(import.meta.url))}`
+ 'websocket-server-worker.js'
)
const requestHandlerWorkerFile = join(
dirname(fileURLToPath(import.meta.url)),
- `request-handler-worker${extname(fileURLToPath(import.meta.url))}`
+ 'request-handler-worker.js'
)
const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
"postpublish": "rm -rf ./package"
},
"engines": {
- "node": ">=22.13.0",
+ "node": "^22.18.0 || >=24.3.0",
"pnpm": ">=9.0.0"
},
"devEngines": {
it('Verify that a negative number of workers is checked', () => {
expect(
() =>
- new FixedClusterPool(-1, './tests/worker-files/cluster/testWorker.cjs')
+ new FixedClusterPool(-1, './tests/worker-files/cluster/testWorker.mjs')
).toThrow(
new RangeError(
'Cannot instantiate a pool with a negative number of workers'
new DynamicClusterPool(
1,
undefined,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
).toThrow(
new TypeError(
new DynamicClusterPool(
0,
0.5,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
).toThrow(
new TypeError(
new DynamicClusterPool(
1,
1,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
).toThrow(
new RangeError(
pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
expect(pool.info).toStrictEqual({
busyWorkerNodes: 0,
it('Verify that pool worker tasks usage are initialized', async () => {
const pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
for (const workerNode of pool.workerNodes) {
expect(workerNode).toBeInstanceOf(WorkerNode)
it('Verify that pool worker tasks queue are initialized', async () => {
let pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
for (const workerNode of pool.workerNodes) {
expect(workerNode).toBeInstanceOf(WorkerNode)
it('Verify that pool worker info are initialized', async () => {
let pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
for (const workerNode of pool.workerNodes) {
expect(workerNode).toBeInstanceOf(WorkerNode)
it('Verify that pool can be started after initialization', async () => {
const pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
startWorkers: false,
}
it('Verify that pool execute() arguments are checked', async () => {
const pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
await expect(pool.execute(undefined, 0)).rejects.toThrow(
new TypeError('name argument must be a string')
it('Verify that pool worker tasks usage are computed', async () => {
const pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
const promises = new Set()
const maxMultiplier = 2
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
expect(pool.emitter.eventNames()).toStrictEqual([])
let poolInfo
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
expect(pool.emitter.eventNames()).toStrictEqual([])
const promises = new Set()
const pool = new DynamicClusterPool(
0,
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
expect(pool.emitter.eventNames()).toStrictEqual([])
const promises = new Set()
for (const { PoolClass, workerFilePath, workerType } of [
{
PoolClass: FixedClusterPool,
- workerFilePath: './tests/worker-files/cluster/asyncWorker.cjs',
+ workerFilePath: './tests/worker-files/cluster/asyncWorker.mjs',
workerType: WorkerTypes.cluster,
},
{
await dynamicThreadPool.destroy()
const fixedClusterPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
+ './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.mjs'
)
await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
expect(fixedClusterPool.hasTaskFunction(DEFAULT_TASK_NAME)).toBe(true)
await dynamicThreadPool.destroy()
const fixedClusterPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
+ './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.mjs'
)
await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
expect(fixedClusterPool.listTaskFunctionsProperties()).toStrictEqual([
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
+ './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.mjs'
)
const data = { n: 10 }
const result0 = await pool.execute(data)
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
const workerNodeKey = 0
await expect(
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
const workerNodeKey = 0
await expect(
const pool = new DynamicClusterPool(
Math.floor(numberOfWorkers / 2),
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
await expect(
pool.sendTaskFunctionOperationToWorkers({
pool = new DynamicClusterPool(
min,
max,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
errorHandler: e => console.error(e),
}
const longRunningPool = new DynamicClusterPool(
min,
max,
- './tests/worker-files/cluster/longRunningWorkerHardBehavior.cjs',
+ './tests/worker-files/cluster/longRunningWorkerHardBehavior.mjs',
{
errorHandler: e => console.error(e),
exitHandler: () => console.info('long executing worker exited'),
const longRunningPool = new DynamicClusterPool(
min,
max,
- './tests/worker-files/cluster/longRunningWorkerSoftBehavior.cjs',
+ './tests/worker-files/cluster/longRunningWorkerSoftBehavior.mjs',
{
errorHandler: e => console.error(e),
exitHandler: () => console.info('long executing worker exited'),
const pool = new DynamicClusterPool(
0,
max,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
expect(pool).toBeInstanceOf(DynamicClusterPool)
// We need to clean up the resources after our test
const pool = new DynamicClusterPool(
0,
max,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
startWorkers: false,
workerChoiceStrategy,
beforeAll(() => {
pool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
errorHandler: e => console.error(e),
}
)
queuePool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
enableTasksQueue: true,
errorHandler: e => console.error(e),
)
emptyPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/emptyWorker.cjs',
+ './tests/worker-files/cluster/emptyWorker.mjs',
{ exitHandler: () => console.info('empty pool worker exited') }
)
echoPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/echoWorker.cjs'
+ './tests/worker-files/cluster/echoWorker.mjs'
)
errorPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/errorWorker.cjs',
+ './tests/worker-files/cluster/errorWorker.mjs',
{
errorHandler: e => console.error(e),
}
)
asyncErrorPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/asyncErrorWorker.cjs',
+ './tests/worker-files/cluster/asyncErrorWorker.mjs',
{
errorHandler: e => console.error(e),
}
)
asyncPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/asyncWorker.cjs'
+ './tests/worker-files/cluster/asyncWorker.mjs'
)
})
})
it('Verify that cluster pool options are checked', async () => {
- const workerFilePath = './tests/worker-files/cluster/testWorker.cjs'
+ const workerFilePath = './tests/worker-files/cluster/testWorker.mjs'
let pool = new FixedClusterPool(numberOfWorkers, workerFilePath)
expect(pool.opts.env).toBeUndefined()
expect(pool.opts.settings).toBeUndefined()
})
it('Verify destroyWorkerNode()', async () => {
- const workerFilePath = './tests/worker-files/cluster/testWorker.cjs'
+ const workerFilePath = './tests/worker-files/cluster/testWorker.mjs'
const pool = new FixedClusterPool(numberOfWorkers, workerFilePath)
const workerNodeKey = 0
let disconnectEvent = 0
it('Verify that a pool with zero worker fails', () => {
expect(
() =>
- new FixedClusterPool(0, './tests/worker-files/cluster/testWorker.cjs')
+ new FixedClusterPool(0, './tests/worker-files/cluster/testWorker.mjs')
).toThrow('Cannot instantiate a fixed pool with zero worker')
})
})
--- /dev/null
+import { describe, expect, it } from 'vitest'
+
+import { DynamicClusterPool, FixedClusterPool } from '../../../lib/index.mjs'
+
+const bounded = async promise => {
+ let timeout
+ try {
+ return await Promise.race([
+ promise,
+ new Promise((resolve, reject) => {
+ timeout = setTimeout(
+ () => reject(new Error('Cluster worker operation remained pending')),
+ 5000
+ )
+ }),
+ ])
+ } finally {
+ clearTimeout(timeout)
+ }
+}
+
+// Start destruction before force-killing on failure, so fixed workers cannot restart.
+const destroyPool = async pool => {
+ const workers = pool.workerNodes.map(workerNode => workerNode.worker)
+ const exits = workers.map(worker => {
+ return worker.isDead()
+ ? Promise.resolve()
+ : new Promise(resolve => worker.once('exit', resolve))
+ })
+ try {
+ await bounded(pool.destroy())
+ expect(workers.every(worker => worker.isDead())).toBe(true)
+ } finally {
+ for (const worker of workers) {
+ if (!worker.isDead()) worker.process.kill('SIGKILL')
+ }
+ await bounded(Promise.all(exits))
+ }
+ expect(workers).toHaveLength(1)
+ expect(pool.info.started).toBe(false)
+}
+
+for (const [poolType, PoolClass, sizes] of [
+ ['fixed', FixedClusterPool, [1]],
+ ['dynamic', DynamicClusterPool, [0, 1]],
+]) {
+ describe(`${poolType} cluster worker module formats`, { retry: 0 }, () => {
+ for (const [format, workerFile] of [
+ ['ESM', './tests/worker-files/cluster/echoWorker.mjs'],
+ ['CommonJS', './tests/worker-files/cluster/echoWorker.cjs'],
+ ]) {
+ it(`executes an immediate task with ${format} and destroys the worker`, async () => {
+ const pool = new PoolClass(...sizes, workerFile, {
+ restartWorkerOnError: false,
+ })
+ const data = {
+ format,
+ nested: { enabled: true, values: [1, 'two', null] },
+ poolType,
+ }
+ try {
+ // Do not wait for online or ready: the first startup message must survive ESM loading.
+ expect(await bounded(pool.execute(data))).toStrictEqual(data)
+ } finally {
+ await destroyPool(pool)
+ }
+ }, 20000)
+ }
+ })
+}
FixedThreadPool,
} from '../../lib/index.mjs'
-const clusterWorker = './tests/worker-files/cluster/testWorker.cjs'
+const clusterWorker = './tests/worker-files/cluster/testWorker.mjs'
const clusterIntrospectionWorker = resolve(
- './tests/worker-files/cluster/prototypePollutionWorker.cjs'
+ './tests/worker-files/cluster/prototypePollutionWorker.mjs'
)
const environmentPollutionKey = 'POOLIFIER_ARBITRARY_ENV_POLLUTION'
const pollutedProperties = [
beforeAll(() => {
clusterFixedPool = new FixedClusterPool(
numberOfWorkers,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
threadFixedPool = new FixedThreadPool(
numberOfThreads,
const pool = new DynamicClusterPool(
min,
max,
- './tests/worker-files/cluster/testWorker.cjs'
+ './tests/worker-files/cluster/testWorker.mjs'
)
pool.setWorkerChoiceStrategy(workerChoiceStrategy)
expect(pool.opts.workerChoiceStrategy).toBe(workerChoiceStrategy)
const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
let pool = new FixedClusterPool(
max,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{ workerChoiceStrategy }
)
let results = new Set()
expect(
createWorker(
WorkerTypes.cluster,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{}
)
).toBeInstanceOf(ClusterWorker)
})
const clusterWorker = createWorker(
WorkerTypes.cluster,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{}
)
const clusterWorkerInfo = initWorkerInfo(clusterWorker)
for (const [workerType, PoolClass, workerFile] of [
['thread', FixedThreadPool, './tests/worker-files/thread/crashWorker.mjs'],
- ['cluster', FixedClusterPool, './tests/worker-files/cluster/crashWorker.cjs'],
+ ['cluster', FixedClusterPool, './tests/worker-files/cluster/crashWorker.mjs'],
]) {
describe(`${workerType} worker exit`, { retry: 0 }, () => {
const createPool = async (size = 1, options = {}) => {
)
clusterWorkerNode = new WorkerNode(
WorkerTypes.cluster,
- './tests/worker-files/cluster/testWorker.cjs',
+ './tests/worker-files/cluster/testWorker.mjs',
{
tasksQueueBackPressureSize: 12,
tasksQueueBucketSize: 6,
it('terminates an already disconnected cluster worker node and can terminate it again after exit', async () => {
const clusterSettings = { ...cluster.settings }
cluster.setupPrimary({
- exec: './tests/worker-files/cluster/crashWorker.cjs',
+ exec: './tests/worker-files/cluster/crashWorker.mjs',
})
let workerNode
try {
workerNode = new WorkerNode(
WorkerTypes.cluster,
- './tests/worker-files/cluster/crashWorker.cjs',
+ './tests/worker-files/cluster/crashWorker.mjs',
{
tasksQueueBackPressureSize: 12,
tasksQueueBucketSize: 6,
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const { sleepTaskFunction } = require('../../test-utils.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import { sleepTaskFunction } from '../../test-utils.cjs'
/**
* Test worker function that throws an error after sleeping.
)
}
-module.exports = new ClusterWorker(error, {
+export default new ClusterWorker(error, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const { sleepTaskFunction } = require('../../test-utils.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import { sleepTaskFunction } from '../../test-utils.cjs'
/**
* Test worker function that sleeps for a specified duration.
return sleepTaskFunction(data, 2000)
}
-module.exports = new ClusterWorker(sleep, {
+export default new ClusterWorker(sleep, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})
-'use strict'
-const cluster = require('node:cluster')
+import cluster from 'node:cluster'
-const { ClusterWorker } = require('../../../lib/index.cjs')
+import { ClusterWorker } from '../../../lib/index.mjs'
const pendingTasks = new Map()
}
})
-module.exports = new ClusterWorker(async data => {
+export default new ClusterWorker(async data => {
if (data.exitCode != null) {
// Exit without an error event to exercise lost task responses.
// eslint-disable-next-line n/no-process-exit
--- /dev/null
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+
+/**
+ * Test worker function that echoes the input data.
+ * @param data - The task data to echo.
+ * @returns The same data that was passed in.
+ */
+function echo (data) {
+ return data
+}
+
+export default new ClusterWorker(echo, {
+ killBehavior: KillBehaviors.HARD,
+})
+++ /dev/null
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-
-/**
- *
- */
-function test () {}
-
-module.exports = new ClusterWorker(test, {
- killBehavior: KillBehaviors.HARD,
- maxInactiveTime: 500,
-})
--- /dev/null
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+
+/**
+ *
+ */
+function test () {}
+
+export default new ClusterWorker(test, {
+ killBehavior: KillBehaviors.HARD,
+ maxInactiveTime: 500,
+})
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
/**
*
throw new Error('Error Message from ClusterWorker')
}
-module.exports = new ClusterWorker(error, {
+export default new ClusterWorker(error, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const { sleepTaskFunction } = require('../../test-utils.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import { sleepTaskFunction } from '../../test-utils.cjs'
/**
* Test worker function for long-running tasks with hard kill behavior.
return sleepTaskFunction(data, 50000)
}
-module.exports = new ClusterWorker(sleep, {
+export default new ClusterWorker(sleep, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})
-'use strict'
-const { ClusterWorker } = require('../../../lib/index.cjs')
-const { sleepTaskFunction } = require('../../test-utils.cjs')
+import { ClusterWorker } from '../../../lib/index.mjs'
+import { sleepTaskFunction } from '../../test-utils.cjs'
/**
* Test worker function that performs a long-running sleep operation with soft behavior.
return sleepTaskFunction(data, 50000)
}
-module.exports = new ClusterWorker(sleep, {
+export default new ClusterWorker(sleep, {
maxInactiveTime: 500,
})
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
/**
* Returns the cluster worker launch context needed by security regressions.
}
}
-module.exports = new ClusterWorker(inspectLaunchContext, {
+export default new ClusterWorker(inspectLaunchContext, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const {
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import {
factorial,
fibonacci,
jsonIntegerSerialization,
-} = require('../../test-utils.cjs')
+} from '../../test-utils.cjs'
-module.exports = new ClusterWorker(
+export default new ClusterWorker(
{
factorial: {
priority: 1,
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const {
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import {
factorial,
fibonacci,
jsonIntegerSerialization,
-} = require('../../test-utils.cjs')
+} from '../../test-utils.cjs'
-module.exports = new ClusterWorker(
+export default new ClusterWorker(
{
factorial: { taskFunction: data => factorial(data.n) },
fibonacci: { priority: -5, taskFunction: data => fibonacci(data.n) },
-'use strict'
-const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const { TaskFunctions } = require('../../test-types.cjs')
-const { executeTaskFunction } = require('../../test-utils.cjs')
+import { ClusterWorker, KillBehaviors } from '../../../lib/index.mjs'
+import { TaskFunctions } from '../../test-types.cjs'
+import { executeTaskFunction } from '../../test-utils.cjs'
/**
* Test worker function that executes configurable task functions for testing.
return executeTaskFunction(data)
}
-module.exports = new ClusterWorker(test, {
+export default new ClusterWorker(test, {
killBehavior: KillBehaviors.HARD,
maxInactiveTime: 500,
})