UNPKG

@overture-stack/lyric

Version:
106 lines (105 loc) 5.29 kB
import { fileURLToPath } from 'node:url'; import * as workerpool from 'workerpool'; import { getLogger } from '../config/logger.js'; const LOG_MODULE = 'WORKER_POOL_MANAGER'; /** * Default worker pool configuration resolver. * * Uses the built JavaScript worker entry file generated at build time. */ const defaultWorkerPoolConfigResolver = () => ({ workerPath: fileURLToPath(new URL('./workerpool.js', import.meta.url)), }); /** * Factory function to create a worker pool with the given configuration. * @param configData The application configuration * @param options Optional worker pool customization (used by tests to inject TS workers). * @returns The worker functions to execute tasks in the worker pool */ export const createWorkerPool = (configData, options) => { const logger = getLogger(configData.logger); // Initialize worker pool const resolveWorkerPoolConfig = options?.resolveWorkerPoolConfig ?? defaultWorkerPoolConfigResolver; const { workerPath, poolOptions } = resolveWorkerPoolConfig(); const pool = workerpool.pool(workerPath, poolOptions); // Cannot send non serializable objects/functions to worker, so we need to create a config object without those properties const workerConfig = { ...configData, auth: { ...configData.auth, customAuthHandler: undefined, }, onFinishCommit: undefined, }; // Create a typed proxy, then initialize the worker through it. // Storing the resulting promise allows commitSubmission to await readiness, // preventing a race where commits run before the worker context is ready. const readyProxy = pool .proxy() .then(async (proxy) => { await proxy.initializeWorker(workerConfig); logger.info(LOG_MODULE, 'Worker pool initialized successfully'); return proxy; }) .catch((error) => { const errMessage = error instanceof Error ? error.message : String(error); logger.error(LOG_MODULE, `Worker pool initialization failed: ${errMessage}`); // this is needed to ensure the error is thrown and propagated immediately during startup, // application should not start if worker initialization fails. queueMicrotask(() => { if (error instanceof Error) { throw error; } throw new Error(errMessage); }); throw error; }); return { commitSubmission: async (input) => { const proxy = await readyProxy; // wait for worker to initialize before using try { const resultCommit = await proxy.commitSubmission(input); // Awaited intentionally: the caller fires commitSubmission without await and returns PROCESSING // to the client immediately, so this does not block the HTTP response. Awaiting here ensures // the publish completes before the worker task is considered complete. // onFinishCommit catches its own errors and never rethrows. if (configData.onFinishCommit && resultCommit) { await configData.onFinishCommit(resultCommit); } } catch (error) { const errMessage = error instanceof Error ? error.message : String(error); logger.error(LOG_MODULE, `Worker pool execution failed for commitSubmission: ${errMessage}`); // Do not re-throw error since commitSubmission in the worker is designed to not throw errors, but log them instead. // This ensures the main thread is not affected by worker errors and can continue processing other tasks. } }, dataValidation: async (input) => { const proxy = await readyProxy; // wait for worker to initialize before using try { await proxy.dataValidation(input); } catch (error) { const errMessage = error instanceof Error ? error.message : String(error); logger.error(LOG_MODULE, `Worker pool execution failed for dataValidation: ${errMessage}`); // Do not re-throw error since dataValidation in the worker is designed to not throw errors, but log them instead. // This ensures the main thread is not affected by worker errors and can continue processing other tasks. } }, dictionaryMigration: async (input) => { const proxy = await readyProxy; // wait for worker to initialize before using try { await proxy.dictionaryMigration(input); } catch (error) { const errMessage = error instanceof Error ? error.message : String(error); logger.error(LOG_MODULE, `Worker pool execution failed for dictionaryMigration: ${errMessage}`); // Do not re-throw error since dictionaryMigration in the worker is designed to not throw errors, but log them instead. // This ensures the main thread is not affected by worker errors and can continue processing other tasks. } }, terminate: async () => { await pool.terminate(); }, }; };