UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint manager to set up, configure and monitor 3D printers. Our aim is to provide extremely optimized websocket performance and reliability.

177 lines (176 loc) 8.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "TaskManagerService", { enumerable: true, get: function() { return TaskManagerService; } }); const _toadscheduler = require("toad-scheduler"); const _awilix = require("awilix"); const _jobexceptions = require("../exceptions/job.exceptions"); const _errorutils = require("../utils/error.utils"); class TaskManagerService { cradleService; toadScheduler; taskStates; logger; constructor(loggerFactory, cradleService, toadScheduler){ this.cradleService = cradleService; this.toadScheduler = toadScheduler; this.taskStates = {}; this.logger = loggerFactory(TaskManagerService.name); } registerJobOrTask(registration) { const { id: taskId, task: serviceIdentifier, preset: schedulerOptions } = registration; try { this.validateInput(taskId, serviceIdentifier, schedulerOptions); } catch (e) { this.logger.error((0, _errorutils.errorSummary)(e), schedulerOptions); return; } const timedTask = this.getSafeTimedTask(taskId, serviceIdentifier); this.taskStates[taskId] = { options: schedulerOptions, timedTask }; if (schedulerOptions.runOnce) { timedTask.execute(); } else if (schedulerOptions.runDelayed) { const delay = (schedulerOptions.milliseconds ?? 0) + (schedulerOptions.seconds ?? 0) * 1000; this.runTimeoutTaskInstance(taskId, delay); } else { this.scheduleEnabledPeriodicJob(taskId); } } scheduleDisabledJob(taskId, failIfEnabled = true) { const taskState = this.getTaskState(taskId); const schedulerOptions = taskState?.options; if (schedulerOptions?.disabled !== true) { if (failIfEnabled) { throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not explicitly disabled and must be running already.`, taskId); } return; } taskState.options.disabled = false; this.scheduleEnabledPeriodicJob(taskId); } disableJob(taskId, failIfDisabled = true) { if (this.isTaskDisabled(taskId)) { if (failIfDisabled) { throw new _jobexceptions.JobValidationException("Can't disable a job which is already disabled", taskId); } return; } const taskState = this.getTaskState(taskId); taskState.options.disabled = true; taskState.job?.stop(); } isTaskDisabled(taskId) { return !!this.getTaskState(taskId).options.disabled; } deregisterTask(taskId) { this.getTaskState(taskId); delete this.taskStates[taskId]; this.toadScheduler.removeById(taskId); } getTaskState(taskId) { const taskState = this.taskStates[taskId]; if (!taskState) { throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not registered`, taskId); } return taskState; } runTimeoutTaskInstance(taskId, timeoutMs) { const taskState = this.getTaskState(taskId); this.logger.log(`Running delayed task '${taskId}' in ${timeoutMs}ms`); setTimeout(()=>taskState.timedTask.execute(), timeoutMs); } stopSchedulerTasks() { this.toadScheduler.stop(); } validateInput(taskId, serviceIdentifier, schedulerOptions) { if (!taskId) { throw new _jobexceptions.JobValidationException("Task ID was not provided. Can't register task or schedule job.", taskId); } const serviceName = serviceIdentifier || "unknown"; const prefix = `Job '${schedulerOptions?.name ?? serviceName}' with ID '${taskId}'`; if (this.taskStates[taskId]) { throw new _jobexceptions.JobValidationException(`${prefix} was already registered. Can't register a key twice.`, taskId); } let resolvedService; try { resolvedService = this.cradleService.resolve(serviceIdentifier); } catch (e) { if (e instanceof _awilix.AwilixResolutionError) { throw new _jobexceptions.JobValidationException(`${prefix} had an awilix dependency resolution error. It can't be scheduled without fixing this problem. Inner error:\n` + e.stack, taskId); } else { throw new _jobexceptions.JobValidationException(`${prefix} is not a registered awilix dependency. It can't be scheduled. Error:\n` + e.stack, taskId); } } if (typeof resolvedService?.run !== "function") { throw new _jobexceptions.JobValidationException(`${prefix} was resolved but it doesn't have a 'run()' method to call.`, taskId); } if (!schedulerOptions?.periodic && !schedulerOptions?.runOnce && !schedulerOptions?.runDelayed) { throw new _jobexceptions.JobValidationException(`${prefix} Provide 'periodic', 'runOnce', or 'runDelayed' option.`, taskId); } if (!schedulerOptions?.periodic && !!schedulerOptions.disabled) { throw new _jobexceptions.JobValidationException(`${prefix} Only tasks of type 'periodic' can be disabled at boot.`, taskId); } if (schedulerOptions?.runDelayed && !schedulerOptions.milliseconds && !schedulerOptions.seconds) { throw new _jobexceptions.JobValidationException(`${prefix} Provide a delayed timing parameter (milliseconds|seconds)`, taskId); } if (schedulerOptions?.periodic && !schedulerOptions.milliseconds && !schedulerOptions.seconds && !schedulerOptions.minutes && !schedulerOptions.hours && !schedulerOptions.days) { throw new _jobexceptions.JobValidationException(`${prefix} Provide a periodic timing parameter (milliseconds|seconds|minutes|hours|days)`, taskId); } } getSafeTimedTask(taskId, serviceIdentifier) { const asyncHandler = async ()=>{ await this.timeTask(taskId, serviceIdentifier); }; return new _toadscheduler.AsyncTask(taskId, asyncHandler, this.getErrorHandler(taskId)); } async timeTask(taskId, serviceIdentifier) { const taskState = this.taskStates[taskId]; taskState.started = Date.now(); const taskService = this.cradleService.resolve(serviceIdentifier); await taskService.run(); taskState.duration = Date.now() - taskState.started; if (taskState.options?.logFirstCompletion !== false && !taskState?.firstCompletion) { this.logger.log(`Task '${taskId}' first completion. Duration ${taskState.duration}ms`); taskState.firstCompletion = Date.now(); } } getErrorHandler(taskId) { return (error)=>{ const taskState = this.taskStates[taskId]; taskState.lastError ??= { time: Date.now(), error }; this.logger.error(`Task '${taskId}' threw an exception: ${error.stack}`); }; } scheduleEnabledPeriodicJob(taskId) { const taskState = this.getTaskState(taskId); if (!taskState?.timedTask || !taskState?.options) { throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} was not registered properly ('timedTask' or 'options' missing).`, taskId); } const schedulerOptions = taskState.options; const timedTask = taskState.timedTask; if (!schedulerOptions?.periodic) { throw new _jobexceptions.JobValidationException(`The requested task with ID ${taskId} is not periodic and cannot be enabled.`, taskId); } if (!schedulerOptions.disabled) { this.logger.log(`Task '${taskId}' was scheduled (runImmediately: ${!!schedulerOptions.runImmediately}).`); const job = new _toadscheduler.SimpleIntervalJob(schedulerOptions, timedTask); taskState.job = job; this.toadScheduler.addSimpleIntervalJob(job); } else { this.logger.log(`Task '${taskId}' was marked as disabled (deferred execution).`); } } } //# sourceMappingURL=task-manager.service.js.map