UNPKG

@cornerstonejs/core

Version:
149 lines (148 loc) 6.77 kB
import * as Comlink from 'comlink'; import { RequestType } from '../enums/index.js'; import { RequestPoolManager } from '../requestPool/requestPoolManager.js'; import { workerLog } from '../utilities/logger.js'; const registrationLog = workerLog.getLogger('registration'); class CentralizedWorkerManager { constructor() { this.workerRegistry = {}; this.workerPoolManager = new RequestPoolManager('webworker'); } registerWorker(workerName, workerFn, options = {}) { const { maxWorkerInstances = 1, overwrite = false, autoTerminateOnIdle = { enabled: false, idleTimeThreshold: 3000, }, } = options; if (this.workerRegistry[workerName] && !overwrite) { registrationLog.warn(`Worker type '${workerName}' is already registered...`); return; } if (overwrite && this.workerRegistry[workerName]?.idleCheckIntervalId) { clearInterval(this.workerRegistry[workerName].idleCheckIntervalId); } const workerProperties = { workerFn: null, instances: [], loadCounters: [], lastActiveTime: [], nativeWorkers: [], autoTerminateOnIdle: autoTerminateOnIdle.enabled, idleCheckIntervalId: null, idleTimeThreshold: autoTerminateOnIdle.idleTimeThreshold, }; workerProperties.loadCounters = Array(maxWorkerInstances).fill(0); workerProperties.lastActiveTime = Array(maxWorkerInstances).fill(null); for (let i = 0; i < maxWorkerInstances; i++) { const worker = workerFn(); workerProperties.instances.push(Comlink.wrap(worker)); workerProperties.nativeWorkers.push(worker); workerProperties.workerFn = workerFn; } this.workerRegistry[workerName] = workerProperties; } getNextWorkerAPI(workerName) { const workerProperties = this.workerRegistry[workerName]; if (!workerProperties) { console.error(`Worker type '${workerName}' is not registered.`); return null; } const workerInstances = workerProperties.instances.filter((instance) => instance !== null); let minLoadIndex = 0; let minLoadValue = workerProperties.loadCounters[0] || 0; for (let i = 1; i < workerInstances.length; i++) { const currentLoadValue = workerProperties.loadCounters[i] || 0; if (currentLoadValue < minLoadValue) { minLoadIndex = i; minLoadValue = currentLoadValue; } } if (workerProperties.instances[minLoadIndex] === null) { const worker = workerProperties.workerFn(); workerProperties.instances[minLoadIndex] = Comlink.wrap(worker); workerProperties.nativeWorkers[minLoadIndex] = worker; } workerProperties.loadCounters[minLoadIndex] += 1; return { api: workerProperties.instances[minLoadIndex], index: minLoadIndex, }; } executeTask(workerName, methodName, args = {}, { requestType = RequestType.Compute, priority = 0, options = {}, callbacks = [], } = {}) { return new Promise((resolve, reject) => { const requestFn = async () => { const { api, index } = this.getNextWorkerAPI(workerName); if (!api) { const error = new Error(`No available worker instance for '${workerName}'`); console.error(error); reject(error); return; } try { let finalCallbacks = []; if (callbacks.length) { finalCallbacks = callbacks.map((cb) => { return Comlink.proxy(cb); }); } const workerProperties = this.workerRegistry[workerName]; workerProperties.processing = true; const results = await api[methodName](args, ...finalCallbacks); workerProperties.processing = false; workerProperties.lastActiveTime[index] = Date.now(); if (workerProperties.autoTerminateOnIdle && !workerProperties.idleCheckIntervalId && workerProperties.idleTimeThreshold) { workerProperties.idleCheckIntervalId = setInterval(() => { this.terminateIdleWorkers(workerName, workerProperties.idleTimeThreshold); }, workerProperties.idleTimeThreshold); } resolve(results); } catch (err) { console.error(`Error executing method '${methodName}' on worker '${workerName}':`, err); reject(err); } finally { this.workerRegistry[workerName].loadCounters[index]--; } }; this.workerPoolManager.addRequest(requestFn, requestType, options, priority); }); } terminateIdleWorkers(workerName, idleTimeThreshold) { const workerProperties = this.workerRegistry[workerName]; if (workerProperties.processing) { return; } const now = Date.now(); workerProperties.instances.forEach((_, index) => { const lastActiveTime = workerProperties.lastActiveTime[index]; const isWorkerActive = lastActiveTime !== null && workerProperties.loadCounters[index] > 0; const idleTime = now - lastActiveTime; if (!isWorkerActive && idleTime > idleTimeThreshold) { this.terminateWorkerInstance(workerName, index); } }); } terminate(workerName) { const workerProperties = this.workerRegistry[workerName]; if (!workerProperties) { console.error(`Worker type '${workerName}' is not registered.`); return; } workerProperties.instances.forEach((_, index) => { this.terminateWorkerInstance(workerName, index); }); } terminateWorkerInstance(workerName, index) { const workerProperties = this.workerRegistry[workerName]; const workerInstance = workerProperties.instances[index]; if (workerInstance !== null) { workerInstance[Comlink.releaseProxy](); workerProperties.nativeWorkers[index].terminate(); workerProperties.instances[index] = null; workerProperties.lastActiveTime[index] = null; } } } export default CentralizedWorkerManager;