import { EHyperpoolErrorCodes, HyperpoolError } from '../errors'; import type { IPoolItem } from '../types'; export interface IStats { free: number; queued: number; running: number; size: number; } export type TContinuationFn = (instance: TInstance) => unknown; /** * AbstractPool */ export abstract class AbstractPool { constructor(protected readonly agents: Map) { if (this.agents.size === 0) { throw new HyperpoolError({ code: EHyperpoolErrorCodes.InvalidConfiguration, message: 'Number of items in pool should be > 0', }); } this.stats.size = this.agents.size; } protected queue: TContinuationFn[] = []; protected poolStats: IStats = { running: 0, queued: 0, free: 0, size: 0, }; get stats(): IStats { return this.poolStats; } get load(): number { return this.stats.running / this.stats.size; } public abstract getNextItem(...args: unknown[]): TValue; public abstract [Symbol.iterator](...args: unknown[]): Generator; public getItemByKey(key: TKey): TValue | undefined { return this.agents.get(key); } public enqueue>(continuation: F): void { this.poolStats.queued++; this.queue.push(continuation); this.drain(); } public drain(): void { if (this.queue.length === 0) { return; } if (this.poolStats.free === 0) { setTimeout(() => { this.drain(); }, 1000).unref(); return; // reschedule } const toExec = this.queue.slice(0, this.stats.free); void Promise.all( toExec.map((continuation) => { return this.execAsync(continuation); }), ); } public async execAsync>( continuation: F, ...args: unknown[] ): Promise> { this.poolStats.running++; this.poolStats.free--; try { const instance = this.getNextItem(...args); const result = await continuation(instance); return result as ReturnType; } finally { this.poolStats.free++; this.poolStats.running--; } } }