Compare commits

..

18 Commits

Author SHA1 Message Date
6115bae66b 3.1.7 2023-10-20 13:05:40 +02:00
1be83af475 fix(core): update 2023-10-20 13:05:39 +02:00
bd14c41edc 3.1.6 2023-08-21 12:40:24 +02:00
d61b79282d fix(core): update 2023-08-21 12:40:24 +02:00
1cba9245fe 3.1.5 2023-08-15 12:02:45 +02:00
4b0db4da21 fix(core): update 2023-08-15 12:02:44 +02:00
a6cd0cb579 3.1.4 2023-08-15 11:39:32 +02:00
bd6109d5ea fix(core): update 2023-08-15 11:39:31 +02:00
aa632a5294 3.1.3 2023-08-13 09:37:24 +02:00
6499dd45cf fix(core): update 2023-08-13 09:37:23 +02:00
d04ed21607 3.1.2 2023-08-12 12:24:11 +02:00
bae776d4e9 fix(core): update 2023-08-12 12:24:10 +02:00
fcd7ea467e 3.1.1 2023-08-12 12:10:38 +02:00
e061b96056 fix(core): update 2023-08-12 12:10:37 +02:00
c2ce669f0c 3.1.0 2023-08-04 13:03:29 +02:00
05f91c3e35 feat(Task): Tasks can now be blocked by other tasks. 2023-08-04 13:03:28 +02:00
94e327c722 3.0.15 2023-08-04 11:58:54 +02:00
57a27604a7 fix(core): update 2023-08-04 11:58:53 +02:00
10 changed files with 1668 additions and 794 deletions

View File

@ -119,6 +119,6 @@ jobs:
run: |
npmci node install stable
npmci npm install
pnpm install -g @gitzone/tsdoc
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true

View File

@ -1,6 +1,6 @@
{
"name": "@push.rocks/taskbuffer",
"version": "3.0.14",
"version": "3.1.7",
"private": false,
"description": "flexible task management. TypeScript ready!",
"main": "dist_ts/index.js",
@ -29,21 +29,21 @@
},
"homepage": "https://gitlab.com/pushrocks/taskbuffer#readme",
"dependencies": {
"@push.rocks/isounique": "^1.0.5",
"@push.rocks/lik": "^6.0.3",
"@push.rocks/lik": "^6.0.5",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartlog": "^3.0.3",
"@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartrx": "^3.0.6",
"@push.rocks/smarttime": "^4.0.4"
"@push.rocks/smarttime": "^4.0.6",
"@push.rocks/smartunique": "^3.0.6"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.66",
"@gitzone/tsbundle": "^2.0.8",
"@gitzone/tsrun": "^1.2.44",
"@gitzone/tstest": "^1.0.77",
"@push.rocks/tapbundle": "^5.0.12",
"@types/node": "^20.4.5"
"@git.zone/tsbuild": "^2.1.66",
"@git.zone/tsbundle": "^2.0.8",
"@git.zone/tsrun": "^1.2.44",
"@git.zone/tstest": "^1.0.77",
"@push.rocks/tapbundle": "^5.0.15",
"@types/node": "^20.8.7"
},
"files": [
"ts/**/*",

2148
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -12,13 +12,6 @@ tap.test('new Task() should return a new task', async () => {
expect(testTask).toBeInstanceOf(taskbuffer.Task);
});
tap.test('should be able to get the task state', async () => {
const testTask = new taskbuffer.Task({
taskFunction: async () => {},
});
expect(testTask.state).toEqual('ready');
});
tap.test('should have bufferMax set to the provided value', async () => {
const task2 = new taskbuffer.Task({
taskFunction: async () => {},

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/taskbuffer',
version: '3.0.14',
version: '3.1.7',
description: 'flexible task management. TypeScript ready!'
}

View File

@ -2,13 +2,11 @@ import { Task } from './taskbuffer.classes.task.js';
import * as plugins from './taskbuffer.plugins.js';
/**
* constains all data for the final coordinator to actually make an informed decision
* Contains all data for the final coordinator to make an informed decision.
*/
export interface IDistributedTaskRequest {
/**
* this needs to correlate to the consultationResult
*/
submitterRandomId: string;
submitterId: string;
requestResponseId: string;
taskName: string;
taskVersion: string;
taskExecutionTime: number;
@ -18,24 +16,23 @@ export interface IDistributedTaskRequest {
}
export interface IDistributedTaskRequestResult {
/**
* this needs to correlate to the decisionInfoBasis
*/
submitterRandomId: string;
/**
* can be used while debugging
*/
submitterId: string;
requestResponseId: string;
considered: boolean;
rank: string;
rank: number;
reason: string;
shouldTrigger: boolean;
}
export abstract class AbstractDistributedCoordinator {
public abstract fireDistributedTaskRequest(
infoBasisArg: IDistributedTaskRequest
infoBasis: IDistributedTaskRequest
): Promise<IDistributedTaskRequestResult>;
public abstract updateDistributedTaskRequest(
infoBasisArg: IDistributedTaskRequest
infoBasis: IDistributedTaskRequest
): Promise<void>;
public abstract start(): Promise<void>;
public abstract stop(): Promise<void>;
}

View File

@ -15,8 +15,9 @@ export interface ITaskSetupFunction<T = undefined> {
export type TPreOrAfterTaskFunction = () => Task<any>;
export class Task<T = undefined> {
// STATIC
public static extractTask<T = undefined>(preOrAfterTaskArg: Task<T> | TPreOrAfterTaskFunction): Task<T> {
public static extractTask<T = undefined>(
preOrAfterTaskArg: Task<T> | TPreOrAfterTaskFunction
): Task<T> {
switch (true) {
case !preOrAfterTaskArg:
return null;
@ -44,7 +45,7 @@ export class Task<T = undefined> {
}
};
public static isTaskTouched<T = undefined> (
public static isTaskTouched<T = undefined>(
taskArg: Task<T> | TPreOrAfterTaskFunction,
touchedTasksArray: Task<T>[]
): boolean {
@ -56,7 +57,7 @@ export class Task<T = undefined> {
}
}
return result;
};
}
public static runTask = async <T>(
taskArg: Task<T> | TPreOrAfterTaskFunction,
@ -65,6 +66,11 @@ export class Task<T = undefined> {
const taskToRun = Task.extractTask(taskArg);
const done = plugins.smartpromise.defer();
// Wait for all blocking tasks to finish
for (const task of taskToRun.blockingTasks) {
await task.finished;
}
if (!taskToRun.setupValue && taskToRun.taskSetup) {
taskToRun.setupValue = await taskToRun.taskSetup();
}
@ -77,6 +83,14 @@ export class Task<T = undefined> {
done.promise.then(async () => {
taskToRun.running = false;
// When the task has finished running, resolve the finished promise
taskToRun.resolveFinished();
// Create a new finished promise for the next run
taskToRun.finished = new Promise((resolve) => {
taskToRun.resolveFinished = resolve;
});
});
const options = {
@ -125,7 +139,6 @@ export class Task<T = undefined> {
return await done.promise;
};
// INSTANCE
public name: string;
public version: string;
public taskFunction: ITaskFunction<T>;
@ -139,12 +152,20 @@ export class Task<T = undefined> {
public preTask: Task<T> | TPreOrAfterTaskFunction;
public afterTask: Task<T> | TPreOrAfterTaskFunction;
// Add a list to store the blocking tasks
public blockingTasks: Task[] = [];
// Add a promise that will resolve when the task has finished
private finished: Promise<void>;
private resolveFinished: () => void;
public running: boolean = false;
public bufferRunner = new BufferRunner(this);
public cycleCounter = new CycleCounter(this);
public idle: boolean = true;
private _state: string = 'ready';
public get idle() {
return !this.running;
}
public taskSetup: ITaskSetupFunction<T>;
public setupValue: T;
@ -162,12 +183,16 @@ export class Task<T = undefined> {
this.taskFunction = optionsArg.taskFunction;
this.preTask = optionsArg.preTask;
this.afterTask = optionsArg.afterTask;
this.idle = !this.running;
this.buffered = optionsArg.buffered;
this.bufferMax = optionsArg.bufferMax;
this.execDelay = optionsArg.execDelay;
this.name = optionsArg.name;
this.taskSetup = optionsArg.taskSetup;
// Create the finished promise
this.finished = new Promise((resolve) => {
this.resolveFinished = resolve;
});
}
public trigger(x?: any): Promise<any> {
@ -185,16 +210,4 @@ export class Task<T = undefined> {
public triggerBuffered(x?: any): Promise<any> {
return this.bufferRunner.trigger(x);
}
get state(): string {
return this._state;
}
set state(stateArg: string) {
if (stateArg === 'locked') {
this._state = 'locked';
} else {
logger.log('error', `state type ${stateArg} could not be set`);
}
}
}

View File

@ -1,10 +1,10 @@
import * as plugins from './taskbuffer.plugins.js';
import { Task } from './taskbuffer.classes.task.js';
import { AbstractDistributedCoordinator } from './taskbuffer.classes.distributedcoordinator.js';
import { AbstractDistributedCoordinator, type IDistributedTaskRequestResult } from './taskbuffer.classes.distributedcoordinator.js';
export interface ICronJob {
cronString: string;
taskNameArg: string;
taskName: string;
job: any;
}
@ -13,57 +13,37 @@ export interface ITaskManagerConstructorOptions {
}
export class TaskManager {
public randomId = plugins.isounique.uni();
public randomId = plugins.smartunique.shortId();
public taskMap = new plugins.lik.ObjectMap<Task>();
private cronJobManager = new plugins.smarttime.CronManager();
public options: ITaskManagerConstructorOptions = {
distributedCoordinator: null,
};
constructor(optionosArg: ITaskManagerConstructorOptions = {}) {
this.options = Object.assign(this.options, optionosArg);
constructor(options: ITaskManagerConstructorOptions = {}) {
this.options = Object.assign(this.options, options);
}
/**
* checks if a task is already present
* @param taskNameArg
*/
public getTaskByName(taskNameArg: string): Task {
return this.taskMap.findSync((itemArg) => {
return itemArg.name === taskNameArg;
});
public getTaskByName(taskName: string): Task {
return this.taskMap.findSync((task) => task.name === taskName);
}
/**
* adds a Task to the TaskManager
* @param taskArg
*/
public addTask(taskArg: Task): void {
if (!taskArg.name) {
throw new Error('taskArg needs a name to be added to taskManager');
public addTask(task: Task): void {
if (!task.name) {
throw new Error('Task must have a name to be added to taskManager');
}
this.taskMap.add(taskArg);
this.taskMap.add(task);
}
/**
* adds and schedules a task at once
* @param taskArg
* @param cronStringArg
*/
public addAndScheduleTask(taskArg: Task, cronStringArg: string) {
this.addTask(taskArg);
this.scheduleTaskByName(taskArg.name, cronStringArg);
public addAndScheduleTask(task: Task, cronString: string) {
this.addTask(task);
this.scheduleTaskByName(task.name, cronString);
}
/**
* triggers a task in the TaskManagerByName
* @param taskNameArg
*/
public triggerTaskByName(taskNameArg: string): Promise<any> {
const taskToTrigger = this.getTaskByName(taskNameArg);
public async triggerTaskByName(taskName: string): Promise<any> {
const taskToTrigger = this.getTaskByName(taskName);
if (!taskToTrigger) {
throw new Error(`There is no task with the name of ${taskNameArg}`);
throw new Error(`No task with the name ${taskName} found.`);
}
return taskToTrigger.trigger();
}
@ -72,88 +52,88 @@ export class TaskManager {
return task.trigger();
}
/**
* schedules the task by name
* @param taskNameArg
*/
public scheduleTaskByName(taskNameArg: string, cronStringArg: string) {
const taskToSchedule = this.getTaskByName(taskNameArg);
const cronJob = this.cronJobManager.addCronjob(
cronStringArg,
async (triggerTimeArg: number) => {
console.log(`taskbuffer schedule triggered task >>${taskToSchedule.name}<<`);
console.log(
`task >>${taskToSchedule.name}<< is ${
taskToSchedule.buffered
? `buffered with max ${taskToSchedule.bufferMax} buffered calls`
: `unbuffered`
}`
);
if (this.options.distributedCoordinator) {
console.log(`Found a distrubuted coordinator, performing distributed consultation.`);
const announcementResult =
await this.options.distributedCoordinator.fireDistributedTaskRequest({
submitterRandomId: this.randomId,
status: 'requesting',
taskExecutionParallel: 1,
taskExecutionTime: triggerTimeArg,
taskExecutionTimeout: taskToSchedule.timeout,
taskName: taskToSchedule.name,
taskVersion: taskToSchedule.version,
});
if (!announcementResult.shouldTrigger) {
console.log('distributed coordinator result: NOT EXECUTING');
return;
} else {
console.log('distributed coordinator result: CHOSEN AND EXECUTING');
}
}
await taskToSchedule.trigger();
}
);
taskToSchedule.cronJob = cronJob;
public scheduleTaskByName(taskName: string, cronString: string) {
const taskToSchedule = this.getTaskByName(taskName);
if (!taskToSchedule) {
throw new Error(`No task with the name ${taskName} found.`);
}
this.handleTaskScheduling(taskToSchedule, cronString);
}
/**
* deschedules a task by name
* @param taskNameArg
*/
public descheduleTaskByName(taskNameArg: string) {
const taskToDeSchedule = this.getTaskByName(taskNameArg);
if (taskToDeSchedule.cronJob) {
this.cronJobManager.removeCronjob(taskToDeSchedule.cronJob);
taskToDeSchedule.cronJob = null;
private handleTaskScheduling(task: Task, cronString: string) {
const cronJob = this.cronJobManager.addCronjob(
cronString,
async (triggerTime: number) => {
this.logTaskState(task);
if (this.options.distributedCoordinator) {
const announcementResult = await this.performDistributedConsultation(task, triggerTime);
if (!announcementResult.shouldTrigger) {
console.log('Distributed coordinator result: NOT EXECUTING');
return;
} else {
console.log('Distributed coordinator result: CHOSEN AND EXECUTING');
}
}
await task.trigger();
}
);
task.cronJob = cronJob;
}
private logTaskState(task: Task) {
console.log(`Taskbuffer schedule triggered task >>${task.name}<<`);
const bufferState = task.buffered
? `buffered with max ${task.bufferMax} buffered calls`
: `unbuffered`;
console.log(`Task >>${task.name}<< is ${bufferState}`);
}
private async performDistributedConsultation(task: Task, triggerTime: number): Promise<IDistributedTaskRequestResult> {
console.log('Found a distributed coordinator, performing consultation.');
return this.options.distributedCoordinator.fireDistributedTaskRequest({
submitterId: this.randomId,
requestResponseId: plugins.smartunique.shortId(),
status: 'requesting',
taskExecutionParallel: 1,
taskExecutionTime: triggerTime,
taskExecutionTimeout: task.timeout,
taskName: task.name,
taskVersion: task.version,
});
}
public descheduleTaskByName(taskName: string) {
const task = this.getTaskByName(taskName);
if (task && task.cronJob) {
this.cronJobManager.removeCronjob(task.cronJob);
task.cronJob = null;
}
if (this.cronJobManager.cronjobs.isEmpty) {
this.cronJobManager.stop();
}
}
/**
* deschedules a task
* @param task
*/
public async descheduleTask(task: Task) {
await this.descheduleTaskByName(task.name);
}
/**
* returns all schedules of a specific task
* @param taskNameArg
*/
public getSchedulesForTaskName(taskNameArg: string) {}
/**
* starts the taskmanager
*/
public start() {
public getScheduleForTaskName(taskName: string): string | null {
const task = this.getTaskByName(taskName);
return task && task.cronJob ? task.cronJob.cronExpression : null;
}
public async start() {
if (this.options.distributedCoordinator) {
await this.options.distributedCoordinator.start();
}
this.cronJobManager.start();
}
/**
* stops the taskmanager
*/
public stop() {
public async stop() {
this.cronJobManager.stop();
if (this.options.distributedCoordinator) {
await this.options.distributedCoordinator.stop();
}
}
}

View File

@ -1,9 +1,9 @@
import * as isounique from '@push.rocks/isounique';
import * as lik from '@push.rocks/lik';
import * as smartlog from '@push.rocks/smartlog';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartrx from '@push.rocks/smartrx';
import * as smarttime from '@push.rocks/smarttime';
import * as smartunique from '@push.rocks/smartunique';
export { isounique, lik, smartlog, smartpromise, smartdelay, smartrx, smarttime };
export { lik, smartlog, smartpromise, smartdelay, smartrx, smarttime, smartunique };

View File

@ -3,9 +3,12 @@
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "nodenext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
}
"verbatimModuleSyntax": true
},
"exclude": [
"dist_*/**/*.d.ts"
]
}