diff --git a/package-lock.json b/package-lock.json index 1329236..59372e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@flybywiresim/igniter", - "version": "1.1.3", + "version": "1.1.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@flybywiresim/igniter", - "version": "1.1.3", + "version": "1.1.4", "bin": { "igniter": "dist/binary.mjs" }, @@ -31,6 +31,7 @@ "reflect-metadata": "^0.1.13", "rollup": "^2.39.0", "rollup-plugin-add-shebang": "^0.3.1", + "task-pool": "^0.1.1", "ts-jest": "^26.5.1", "tslib": "^2.1.0", "tsyringe": "^4.4.0", @@ -8994,6 +8995,15 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/task-pool": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/task-pool/-/task-pool-0.1.1.tgz", + "integrity": "sha1-h3Updv250fthw224DWV37mAY/SE=", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -16792,6 +16802,12 @@ } } }, + "task-pool": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/task-pool/-/task-pool-0.1.1.tgz", + "integrity": "sha1-h3Updv250fthw224DWV37mAY/SE=", + "dev": true + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", diff --git a/package.json b/package.json index cb79211..bcd32ad 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "reflect-metadata": "^0.1.13", "rollup": "^2.39.0", "rollup-plugin-add-shebang": "^0.3.1", + "task-pool": "^0.1.1", "ts-jest": "^26.5.1", "tslib": "^2.1.0", "tsyringe": "^4.4.0", diff --git a/src/Binary.ts b/src/Binary.ts index bc53bdd..f3f75bf 100644 --- a/src/Binary.ts +++ b/src/Binary.ts @@ -1,4 +1,5 @@ import { Command } from 'commander'; +import { Pool } from 'task-pool'; import { findConfigPath, loadConfigTask } from './Helpers'; import { Context } from './Library/Contracts/Context'; import { version } from '../package.json'; @@ -7,6 +8,7 @@ import Cache from './Cache'; const binary = (new Command()).version(version) .option('-c, --config <filename>', 'set the configuration file name', 'igniter.config.mjs') + .option('-j, --num-workers <number>', 'set the maximum number of workers to use', `${Number.MAX_SAFE_INTEGER}`) .option('-r, --regex <regex>', 'regular expression used to filter tasks') .option('-i, --invert', 'if true, regex will be used to reject tasks') .option('--no-cache', 'do not skip tasks, even if hash matches cache') @@ -24,6 +26,7 @@ const context: Context = { dryRun: options.dryRun, filterRegex: options.regex ? RegExp(options.regex) : undefined, invertRegex: options.invert, + taskPool: new Pool({ limit: options.numWorkers }), }; // Create and register a cache if needed. diff --git a/src/Library/Contracts/Context.ts b/src/Library/Contracts/Context.ts index 24d7c97..c00d9b1 100644 --- a/src/Library/Contracts/Context.ts +++ b/src/Library/Contracts/Context.ts @@ -1,3 +1,4 @@ +import { Pool } from 'task-pool'; import { Cache } from './Cache'; export interface Context { @@ -7,4 +8,5 @@ export interface Context { filterRegex: RegExp|undefined, invertRegex: boolean, cache?: Cache, + taskPool: Pool, } diff --git a/src/Library/Tasks/ExecTask.ts b/src/Library/Tasks/ExecTask.ts index d3cd6d2..2c7c9a5 100644 --- a/src/Library/Tasks/ExecTask.ts +++ b/src/Library/Tasks/ExecTask.ts @@ -1,6 +1,7 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import GenericTask from './GenericTask'; +import { TaskStatus } from '../Contracts/Task'; export default class ExecTask extends GenericTask { constructor( @@ -10,7 +11,18 @@ export default class ExecTask extends GenericTask { ) { const commands = typeof command === 'string' ? [command] : command; super(key, async () => { - for await (const cmd of commands) await promisify(exec)(cmd); + for await (const cmd of commands) { + const poolExec = this.context.taskPool.promise.wrap(promisify(exec)); + + const task = poolExec(cmd); + + this.status = TaskStatus.Queued; + task.on('run', () => { + this.status = TaskStatus.Running; + }); + + await task; + } }, hashFolders); } } diff --git a/src/task-pool.d.ts b/src/task-pool.d.ts new file mode 100644 index 0000000..e7c781f --- /dev/null +++ b/src/task-pool.d.ts @@ -0,0 +1,51 @@ +declare module "task-pool" { + export type PoolStyle = 'callback' | 'promise' + + export interface PoolInit { + timeout: number, + style: PoolStyle, + limit: number, + } + + export interface PoolTaskOptions { + timeout: number, + style: PoolStyle, + } + + export type PoolTaskFunction<TArgs> = (...args: TArgs[]) => any + + export interface PoolTask<TFnArgs> { + fn: PoolTaskFunction<TFnArgs>, + timeout: number, + style: PoolStyle, + args: any[], + on: (event: string, fn: (event: any) => void) => void, + } + + export type PoolTaskFactory<TPoolStyle extends PoolStyle> = <TArgs>(...args: TArgs[]) => + (TPoolStyle extends 'callback' ? PoolTask<TArgs> : Promise<any> & PoolTask<any>) + + export class Pool<TPoolStyle extends PoolStyle = 'callback'> { + constructor(init: Partial<PoolInit>) + + timeout: Pick<PoolInit, 'timeout'> + + style: TPoolStyle + + limit: Pick<PoolInit, 'limit'> + + running: number + + queue: PoolTask<any>[] + + wrap(Function, options?: Partial<PoolTaskOptions>): PoolTaskFactory<TPoolStyle> + + next() + + start(): boolean + + add(task: PoolTask<any>) + + get promise(): Pool<'promise'> + } +}