@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
JavaScript
;
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