X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fworker%2Fabstract-worker.ts;h=8cd370de2e3b7671c2f697c106acff3dc12e267f;hb=edbc15c62f491f28a09a03a4cc134a8a9522059e;hp=75a8d784f87d4cd18126d2f81ce9ba54fae65323;hpb=85bb5e488cb5adb8fef806e1f3fd9f85c525713e;p=poolifier.git diff --git a/src/worker/abstract-worker.ts b/src/worker/abstract-worker.ts index 75a8d784..8cd370de 100644 --- a/src/worker/abstract-worker.ts +++ b/src/worker/abstract-worker.ts @@ -22,6 +22,11 @@ import type { TaskSyncFunction } from './task-functions' +interface TaskFunctionOperationReturnType { + status: boolean + error?: Error +} + const DEFAULT_MAX_INACTIVE_TIME = 60000 const DEFAULT_WORKER_OPTIONS: WorkerOptions = { /** @@ -88,10 +93,13 @@ export abstract class AbstractWorker< protected opts: WorkerOptions = DEFAULT_WORKER_OPTIONS ) { super(type) - this.checkWorkerOptions(this.opts) + if (this.isMain == null) { + throw new Error('isMain parameter is mandatory') + } this.checkTaskFunctions(taskFunctions) + this.checkWorkerOptions(this.opts) if (!this.isMain) { - this.getMainWorker()?.on('message', this.handleReadyMessage.bind(this)) + this.getMainWorker().on('message', this.handleReadyMessage.bind(this)) } } @@ -100,6 +108,27 @@ export abstract class AbstractWorker< delete this.opts.async } + private checkValidTaskFunction ( + name: string, + fn: TaskFunction + ): void { + if (typeof name !== 'string') { + throw new TypeError( + 'A taskFunctions parameter object key is not a string' + ) + } + if (typeof name === 'string' && name.trim().length === 0) { + throw new TypeError( + 'A taskFunctions parameter object key is an empty string' + ) + } + if (typeof fn !== 'function') { + throw new TypeError( + 'A taskFunctions parameter object value is not a function' + ) + } + } + /** * Checks if the `taskFunctions` parameter is passed to the constructor. * @@ -125,21 +154,7 @@ export abstract class AbstractWorker< } else if (isPlainObject(taskFunctions)) { let firstEntry = true for (const [name, fn] of Object.entries(taskFunctions)) { - if (typeof name !== 'string') { - throw new TypeError( - 'A taskFunctions parameter object key is not a string' - ) - } - if (typeof name === 'string' && name.trim().length === 0) { - throw new TypeError( - 'A taskFunctions parameter object key an empty string' - ) - } - if (typeof fn !== 'function') { - throw new TypeError( - 'A taskFunctions parameter object value is not a function' - ) - } + this.checkValidTaskFunction(name, fn) const boundFn = fn.bind(this) if (firstEntry) { this.taskFunctions.set(DEFAULT_TASK_NAME, boundFn) @@ -162,11 +177,14 @@ export abstract class AbstractWorker< * * @param name - The name of the task function to check. * @returns Whether the worker has a task function with the given name or not. - * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string. */ - public hasTaskFunction (name: string): boolean { - this.checkTaskFunctionName(name) - return this.taskFunctions.has(name) + public hasTaskFunction (name: string): TaskFunctionOperationReturnType { + try { + this.checkTaskFunctionName(name) + } catch (error) { + return { status: false, error: error as Error } + } + return { status: this.taskFunctions.has(name) } } /** @@ -176,24 +194,21 @@ export abstract class AbstractWorker< * @param name - The name of the task function to add. * @param fn - The task function to add. * @returns Whether the task function was added or not. - * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the `name` parameter is the default task function reserved name. - * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `fn` parameter is not a function. */ public addTaskFunction ( name: string, fn: TaskFunction - ): boolean { - this.checkTaskFunctionName(name) - if (name === DEFAULT_TASK_NAME) { - throw new Error( - 'Cannot add a task function with the default reserved name' - ) - } - if (typeof fn !== 'function') { - throw new TypeError('fn parameter is not a function') - } + ): TaskFunctionOperationReturnType { try { + this.checkTaskFunctionName(name) + if (name === DEFAULT_TASK_NAME) { + throw new Error( + 'Cannot add a task function with the default reserved name' + ) + } + if (typeof fn !== 'function') { + throw new TypeError('fn parameter is not a function') + } const boundFn = fn.bind(this) if ( this.taskFunctions.get(name) === @@ -203,9 +218,9 @@ export abstract class AbstractWorker< } this.taskFunctions.set(name, boundFn) this.sendTaskFunctionsListToMainWorker() - return true - } catch { - return false + return { status: true } + } catch (error) { + return { status: false, error: error as Error } } } @@ -214,27 +229,29 @@ export abstract class AbstractWorker< * * @param name - The name of the task function to remove. * @returns Whether the task function existed and was removed or not. - * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the `name` parameter is the default task function reserved name. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the `name` parameter is the task function used as default task function. */ - public removeTaskFunction (name: string): boolean { - this.checkTaskFunctionName(name) - if (name === DEFAULT_TASK_NAME) { - throw new Error( - 'Cannot remove the task function with the default reserved name' - ) - } - if ( - this.taskFunctions.get(name) === this.taskFunctions.get(DEFAULT_TASK_NAME) - ) { - throw new Error( - 'Cannot remove the task function used as the default task function' - ) + public removeTaskFunction (name: string): TaskFunctionOperationReturnType { + try { + this.checkTaskFunctionName(name) + if (name === DEFAULT_TASK_NAME) { + throw new Error( + 'Cannot remove the task function with the default reserved name' + ) + } + if ( + this.taskFunctions.get(name) === + this.taskFunctions.get(DEFAULT_TASK_NAME) + ) { + throw new Error( + 'Cannot remove the task function used as the default task function' + ) + } + const deleteStatus = this.taskFunctions.delete(name) + this.sendTaskFunctionsListToMainWorker() + return { status: deleteStatus } + } catch (error) { + return { status: false, error: error as Error } } - const deleteStatus = this.taskFunctions.delete(name) - this.sendTaskFunctionsListToMainWorker() - return deleteStatus } /** @@ -242,7 +259,7 @@ export abstract class AbstractWorker< * * @returns The names of the worker's task functions. */ - public listTaskFunctions (): string[] { + public listTaskFunctionNames (): string[] { const names: string[] = [...this.taskFunctions.keys()] let defaultTaskFunctionName: string = DEFAULT_TASK_NAME for (const [name, fn] of this.taskFunctions) { @@ -258,7 +275,7 @@ export abstract class AbstractWorker< names[names.indexOf(DEFAULT_TASK_NAME)], defaultTaskFunctionName, ...names.filter( - (name) => name !== DEFAULT_TASK_NAME && name !== defaultTaskFunctionName + name => name !== DEFAULT_TASK_NAME && name !== defaultTaskFunctionName ) ] } @@ -268,30 +285,27 @@ export abstract class AbstractWorker< * * @param name - The name of the task function to use as default task function. * @returns Whether the default task function was set or not. - * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the `name` parameter is the default task function reserved name. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the `name` parameter is a non-existing task function. */ - public setDefaultTaskFunction (name: string): boolean { - this.checkTaskFunctionName(name) - if (name === DEFAULT_TASK_NAME) { - throw new Error( - 'Cannot set the default task function reserved name as the default task function' - ) - } - if (!this.taskFunctions.has(name)) { - throw new Error( - 'Cannot set the default task function to a non-existing task function' - ) - } + public setDefaultTaskFunction (name: string): TaskFunctionOperationReturnType { try { + this.checkTaskFunctionName(name) + if (name === DEFAULT_TASK_NAME) { + throw new Error( + 'Cannot set the default task function reserved name as the default task function' + ) + } + if (!this.taskFunctions.has(name)) { + throw new Error( + 'Cannot set the default task function to a non-existing task function' + ) + } this.taskFunctions.set( DEFAULT_TASK_NAME, this.taskFunctions.get(name) as TaskFunction ) - return true - } catch { - return false + return { status: true } + } catch (error) { + return { status: false, error: error as Error } } } @@ -324,6 +338,9 @@ export abstract class AbstractWorker< } else if (message.checkActive != null) { // Check active message received message.checkActive ? this.startCheckActive() : this.stopCheckActive() + } else if (message.taskFunctionOperation != null) { + // Task function operation message received + this.handleTaskFunctionOperationMessage(message) } else if (message.taskId != null && message.data != null) { // Task message received this.run(message) @@ -333,6 +350,36 @@ export abstract class AbstractWorker< } } + protected handleTaskFunctionOperationMessage ( + message: MessageValue + ): void { + const { taskFunctionOperation, taskFunction, taskFunctionName } = message + let response!: TaskFunctionOperationReturnType + if (taskFunctionOperation === 'add') { + response = this.addTaskFunction( + taskFunctionName as string, + // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func + new Function(`return ${taskFunction as string}`)() as TaskFunction< + Data, + Response + > + ) + } else if (taskFunctionOperation === 'remove') { + response = this.removeTaskFunction(taskFunctionName as string) + } else if (taskFunctionOperation === 'default') { + response = this.setDefaultTaskFunction(taskFunctionName as string) + } + this.sendToMainWorker({ + taskFunctionOperation, + taskFunctionOperationStatus: response.status, + workerError: { + name: taskFunctionName as string, + message: this.handleError(response.error as Error | string) + }, + workerId: this.id + }) + } + /** * Handles a kill message sent by the main worker. * @@ -419,6 +466,7 @@ export abstract class AbstractWorker< * Returns the main worker. * * @returns Reference to the main worker. + * @throws {@link https://nodejs.org/api/errors.html#class-error} If the main worker is not set. */ protected getMainWorker (): MainWorker { if (this.mainWorker == null) { @@ -441,7 +489,7 @@ export abstract class AbstractWorker< */ protected sendTaskFunctionsListToMainWorker (): void { this.sendToMainWorker({ - taskFunctions: this.listTaskFunctions(), + taskFunctionNames: this.listTaskFunctionNames(), workerId: this.id }) } @@ -449,11 +497,11 @@ export abstract class AbstractWorker< /** * Handles an error and convert it to a string so it can be sent back to the main worker. * - * @param e - The error raised by the worker. + * @param error - The error raised by the worker. * @returns The error message. */ - protected handleError (e: Error | string): string { - return e instanceof Error ? e.message : e + protected handleError (error: Error | string): string { + return error instanceof Error ? error.message : error } /** @@ -463,7 +511,20 @@ export abstract class AbstractWorker< * @throws {@link https://nodejs.org/api/errors.html#class-error} If the task function is not found. */ protected run (task: Task): void { - const fn = this.getTaskFunction(task.name) + const { name, taskId, data } = task + const fn = this.taskFunctions.get(name ?? DEFAULT_TASK_NAME) + if (fn == null) { + this.sendToMainWorker({ + workerError: { + name: name as string, + message: `Task function '${name as string}' not found`, + data + }, + workerId: this.id, + taskId + }) + return + } if (isAsyncFunction(fn)) { this.runInAsyncScope(this.runAsync.bind(this), this, fn, task) } else { @@ -492,12 +553,11 @@ export abstract class AbstractWorker< workerId: this.id, taskId }) - } catch (e) { - const errorMessage = this.handleError(e as Error | string) + } catch (error) { this.sendToMainWorker({ - taskError: { - name: name ?? DEFAULT_TASK_NAME, - message: errorMessage, + workerError: { + name: name as string, + message: this.handleError(error as Error | string), data }, workerId: this.id, @@ -521,7 +581,7 @@ export abstract class AbstractWorker< const { name, taskId, data } = task let taskPerformance = this.beginTaskPerformance(name) fn(data) - .then((res) => { + .then(res => { taskPerformance = this.endTaskPerformance(taskPerformance) this.sendToMainWorker({ data: res, @@ -531,12 +591,11 @@ export abstract class AbstractWorker< }) return null }) - .catch((e) => { - const errorMessage = this.handleError(e as Error | string) + .catch(error => { this.sendToMainWorker({ - taskError: { - name: name ?? DEFAULT_TASK_NAME, - message: errorMessage, + workerError: { + name: name as string, + message: this.handleError(error as Error | string), data }, workerId: this.id, @@ -549,22 +608,6 @@ export abstract class AbstractWorker< .catch(EMPTY_FUNCTION) } - /** - * Gets the task function with the given name. - * - * @param name - Name of the task function that will be returned. - * @returns The task function. - * @throws {@link https://nodejs.org/api/errors.html#class-error} If the task function is not found. - */ - private getTaskFunction (name?: string): TaskFunction { - name = name ?? DEFAULT_TASK_NAME - const fn = this.taskFunctions.get(name) - if (fn == null) { - throw new Error(`Task function '${name}' not found`) - } - return fn - } - private beginTaskPerformance (name?: string): TaskPerformance { this.checkStatistics() return {