diff --git a/src/classes/child-pool.ts b/src/classes/child-pool.ts index c934f691842..ca51f641f14 100644 --- a/src/classes/child-pool.ts +++ b/src/classes/child-pool.ts @@ -21,6 +21,21 @@ export class ChildPool { free: { [key: string]: Child[] } = {}; private opts: ChildPoolOpts; + /** + * Creates a new ChildPool that manages a set of sandboxed child processes + * (or worker threads) used to run job processors. + * + * @param opts - Pool options. + * @param opts.mainFile - Path to the main bootstrap file loaded inside each + * child. Defaults to the bundled CJS or ESM `main.js` depending on the + * runtime module system. + * @param opts.useWorkerThreads - If true, spawn worker threads instead of + * forked child processes. + * @param opts.workerForkOptions - Options forwarded to `child_process.fork` + * when not using worker threads. + * @param opts.workerThreadsOptions - Options forwarded to the `Worker` + * constructor when using worker threads. + */ constructor({ mainFile = supportCJS() ? path.join(process.cwd(), 'dist/cjs/classes/main.js') @@ -37,6 +52,14 @@ export class ChildPool { }; } + /** + * Retains a child for the given processor file. Reuses a free child when + * one is available, otherwise spawns and initializes a new one. + * + * @param processFile - Absolute path to the processor file the child will + * load and execute jobs from. + * @returns A ready-to-use child instance bound to `processFile`. + */ async retain(processFile: string): Promise { let child = this.getFree(processFile).pop(); @@ -72,11 +95,23 @@ export class ChildPool { } } + /** + * Releases a previously retained child back to the free pool so it can be + * reused by a subsequent `retain` call. + * + * @param child - The child instance to release. + */ release(child: Child): void { delete this.retained[child.pid]; this.getFree(child.processFile).push(child); } + /** + * Removes a child from both the retained map and the free pool. Typically + * called when the underlying process or worker has exited. + * + * @param child - The child instance to remove from the pool. + */ remove(child: Child): void { delete this.retained[child.pid]; @@ -88,6 +123,13 @@ export class ChildPool { } } + /** + * Removes a child from the pool and terminates it with the given signal. + * If the child does not exit within the kill timeout it is force-killed. + * + * @param child - The child instance to terminate. + * @param signal - Signal used to terminate the child. Defaults to `SIGKILL`. + */ async kill( child: Child, signal: 'SIGTERM' | 'SIGKILL' = 'SIGKILL', @@ -96,6 +138,11 @@ export class ChildPool { return child.kill(signal, CHILD_KILL_TIMEOUT); } + /** + * Terminates every child currently tracked by the pool (both retained and + * free) and clears all internal references. Resolves once every child has + * been killed. + */ async clean(): Promise { const children = Object.values(this.retained).concat(this.getAllFree()); this.retained = {}; @@ -104,10 +151,22 @@ export class ChildPool { await Promise.all(children.map(c => this.kill(c, 'SIGTERM'))); } + /** + * Returns the array of free children for a given processor file, creating + * an empty entry on first access. + * + * @param id - Processor file path used as the pool key. + * @returns The mutable array of free children for `id`. + */ getFree(id: string): Child[] { return (this.free[id] = this.free[id] || []); } + /** + * Returns every free child across all processor files in the pool. + * + * @returns A flat array containing every free child instance. + */ getAllFree(): Child[] { return Object.values(this.free).reduce( (first, second) => first.concat(second),