@push.rocks/taskbuffer 4.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/index.js +2 -2
- package/dist_ts/taskbuffer.classes.bufferrunner.d.ts +1 -1
- package/dist_ts/taskbuffer.classes.bufferrunner.js +1 -1
- package/dist_ts/taskbuffer.classes.cyclecounter.d.ts +1 -1
- package/dist_ts/taskbuffer.classes.cyclecounter.js +1 -1
- package/dist_ts/taskbuffer.classes.task.d.ts +12 -15
- package/dist_ts/taskbuffer.classes.task.js +2 -27
- package/dist_ts/taskbuffer.classes.taskconstraintgroup.d.ts +18 -0
- package/dist_ts/taskbuffer.classes.taskconstraintgroup.js +64 -0
- package/dist_ts/taskbuffer.classes.taskmanager.d.ts +18 -9
- package/dist_ts/taskbuffer.classes.taskmanager.js +106 -7
- package/dist_ts/taskbuffer.interfaces.d.ts +12 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +1 -1
- package/readme.hints.md +29 -7
- package/readme.md +220 -61
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -2
- package/ts/taskbuffer.classes.bufferrunner.ts +1 -1
- package/ts/taskbuffer.classes.cyclecounter.ts +1 -1
- package/ts/taskbuffer.classes.task.ts +18 -54
- package/ts/taskbuffer.classes.taskconstraintgroup.ts +80 -0
- package/ts/taskbuffer.classes.taskmanager.ts +155 -33
- package/ts/taskbuffer.interfaces.ts +14 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/dist_ts/taskbuffer.classes.taskrunner.d.ts +0 -30
- package/dist_ts/taskbuffer.classes.taskrunner.js +0 -60
- package/ts/taskbuffer.classes.taskrunner.ts +0 -69
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Task } from './taskbuffer.classes.task.js';
|
|
2
|
+
import type { ITaskConstraintGroupOptions } from './taskbuffer.interfaces.js';
|
|
3
|
+
|
|
4
|
+
export class TaskConstraintGroup<TData extends Record<string, unknown> = Record<string, unknown>> {
|
|
5
|
+
public name: string;
|
|
6
|
+
public maxConcurrent: number;
|
|
7
|
+
public cooldownMs: number;
|
|
8
|
+
private constraintKeyForTask: (task: Task<any, any, TData>) => string | null | undefined;
|
|
9
|
+
|
|
10
|
+
private runningCounts = new Map<string, number>();
|
|
11
|
+
private lastCompletionTimes = new Map<string, number>();
|
|
12
|
+
|
|
13
|
+
constructor(options: ITaskConstraintGroupOptions<TData>) {
|
|
14
|
+
this.name = options.name;
|
|
15
|
+
this.constraintKeyForTask = options.constraintKeyForTask;
|
|
16
|
+
this.maxConcurrent = options.maxConcurrent ?? Infinity;
|
|
17
|
+
this.cooldownMs = options.cooldownMs ?? 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getConstraintKey(task: Task<any, any, TData>): string | null {
|
|
21
|
+
const key = this.constraintKeyForTask(task);
|
|
22
|
+
return key ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public canRun(subGroupKey: string): boolean {
|
|
26
|
+
const running = this.runningCounts.get(subGroupKey) ?? 0;
|
|
27
|
+
if (running >= this.maxConcurrent) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (this.cooldownMs > 0) {
|
|
32
|
+
const lastCompletion = this.lastCompletionTimes.get(subGroupKey);
|
|
33
|
+
if (lastCompletion !== undefined) {
|
|
34
|
+
const elapsed = Date.now() - lastCompletion;
|
|
35
|
+
if (elapsed < this.cooldownMs) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public acquireSlot(subGroupKey: string): void {
|
|
45
|
+
const current = this.runningCounts.get(subGroupKey) ?? 0;
|
|
46
|
+
this.runningCounts.set(subGroupKey, current + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public releaseSlot(subGroupKey: string): void {
|
|
50
|
+
const current = this.runningCounts.get(subGroupKey) ?? 0;
|
|
51
|
+
const next = Math.max(0, current - 1);
|
|
52
|
+
if (next === 0) {
|
|
53
|
+
this.runningCounts.delete(subGroupKey);
|
|
54
|
+
} else {
|
|
55
|
+
this.runningCounts.set(subGroupKey, next);
|
|
56
|
+
}
|
|
57
|
+
this.lastCompletionTimes.set(subGroupKey, Date.now());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public getCooldownRemaining(subGroupKey: string): number {
|
|
61
|
+
if (this.cooldownMs <= 0) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const lastCompletion = this.lastCompletionTimes.get(subGroupKey);
|
|
65
|
+
if (lastCompletion === undefined) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const elapsed = Date.now() - lastCompletion;
|
|
69
|
+
return Math.max(0, this.cooldownMs - elapsed);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public getRunningCount(subGroupKey: string): number {
|
|
73
|
+
return this.runningCounts.get(subGroupKey) ?? 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public reset(): void {
|
|
77
|
+
this.runningCounts.clear();
|
|
78
|
+
this.lastCompletionTimes.clear();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as plugins from './taskbuffer.plugins.js';
|
|
2
2
|
import { Task } from './taskbuffer.classes.task.js';
|
|
3
|
+
import { TaskConstraintGroup } from './taskbuffer.classes.taskconstraintgroup.js';
|
|
3
4
|
import {
|
|
4
5
|
AbstractDistributedCoordinator,
|
|
5
6
|
type IDistributedTaskRequestResult,
|
|
6
7
|
} from './taskbuffer.classes.distributedcoordinator.js';
|
|
7
|
-
import type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent } from './taskbuffer.interfaces.js';
|
|
8
|
+
import type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, IConstrainedTaskEntry } from './taskbuffer.interfaces.js';
|
|
8
9
|
import { logger } from './taskbuffer.logging.js';
|
|
9
10
|
|
|
10
11
|
export interface ICronJob {
|
|
@@ -19,23 +20,28 @@ export interface ITaskManagerConstructorOptions {
|
|
|
19
20
|
|
|
20
21
|
export class TaskManager {
|
|
21
22
|
public randomId = plugins.smartunique.shortId();
|
|
22
|
-
public taskMap = new plugins.lik.ObjectMap<Task<any, any>>();
|
|
23
|
+
public taskMap = new plugins.lik.ObjectMap<Task<any, any, any>>();
|
|
23
24
|
public readonly taskSubject = new plugins.smartrx.rxjs.Subject<ITaskEvent>();
|
|
24
|
-
private taskSubscriptions = new Map<Task<any, any>, plugins.smartrx.rxjs.Subscription>();
|
|
25
|
+
private taskSubscriptions = new Map<Task<any, any, any>, plugins.smartrx.rxjs.Subscription>();
|
|
25
26
|
private cronJobManager = new plugins.smarttime.CronManager();
|
|
26
27
|
public options: ITaskManagerConstructorOptions = {
|
|
27
28
|
distributedCoordinator: null,
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
// Constraint system
|
|
32
|
+
public constraintGroups: TaskConstraintGroup<any>[] = [];
|
|
33
|
+
private constraintQueue: IConstrainedTaskEntry[] = [];
|
|
34
|
+
private drainTimer: ReturnType<typeof setTimeout> | null = null;
|
|
35
|
+
|
|
30
36
|
constructor(options: ITaskManagerConstructorOptions = {}) {
|
|
31
37
|
this.options = Object.assign(this.options, options);
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
public getTaskByName(taskName: string): Task<any, any> {
|
|
40
|
+
public getTaskByName(taskName: string): Task<any, any, any> {
|
|
35
41
|
return this.taskMap.findSync((task) => task.name === taskName);
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
public addTask(task: Task<any, any>): void {
|
|
44
|
+
public addTask(task: Task<any, any, any>): void {
|
|
39
45
|
if (!task.name) {
|
|
40
46
|
throw new Error('Task must have a name to be added to taskManager');
|
|
41
47
|
}
|
|
@@ -46,7 +52,7 @@ export class TaskManager {
|
|
|
46
52
|
this.taskSubscriptions.set(task, subscription);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
public removeTask(task: Task<any, any>): void {
|
|
55
|
+
public removeTask(task: Task<any, any, any>): void {
|
|
50
56
|
this.taskMap.remove(task);
|
|
51
57
|
const subscription = this.taskSubscriptions.get(task);
|
|
52
58
|
if (subscription) {
|
|
@@ -55,21 +61,134 @@ export class TaskManager {
|
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
public addAndScheduleTask(task: Task<any, any>, cronString: string) {
|
|
64
|
+
public addAndScheduleTask(task: Task<any, any, any>, cronString: string) {
|
|
59
65
|
this.addTask(task);
|
|
60
66
|
this.scheduleTaskByName(task.name, cronString);
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
// Constraint group management
|
|
70
|
+
public addConstraintGroup(group: TaskConstraintGroup<any>): void {
|
|
71
|
+
this.constraintGroups.push(group);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public removeConstraintGroup(name: string): void {
|
|
75
|
+
this.constraintGroups = this.constraintGroups.filter((g) => g.name !== name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Core constraint evaluation
|
|
79
|
+
public async triggerTaskConstrained(task: Task<any, any, any>, input?: any): Promise<any> {
|
|
80
|
+
// Gather applicable constraints
|
|
81
|
+
const applicableGroups: Array<{ group: TaskConstraintGroup<any>; key: string }> = [];
|
|
82
|
+
for (const group of this.constraintGroups) {
|
|
83
|
+
const key = group.getConstraintKey(task);
|
|
84
|
+
if (key !== null) {
|
|
85
|
+
applicableGroups.push({ group, key });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// No constraints apply → trigger directly
|
|
90
|
+
if (applicableGroups.length === 0) {
|
|
91
|
+
return task.trigger(input);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if all constraints allow running
|
|
95
|
+
const allCanRun = applicableGroups.every(({ group, key }) => group.canRun(key));
|
|
96
|
+
if (allCanRun) {
|
|
97
|
+
return this.executeWithConstraintTracking(task, input, applicableGroups);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Blocked → enqueue with deferred promise
|
|
101
|
+
const deferred = plugins.smartpromise.defer<any>();
|
|
102
|
+
this.constraintQueue.push({ task, input, deferred });
|
|
103
|
+
return deferred.promise;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async executeWithConstraintTracking(
|
|
107
|
+
task: Task<any, any, any>,
|
|
108
|
+
input: any,
|
|
109
|
+
groups: Array<{ group: TaskConstraintGroup<any>; key: string }>,
|
|
110
|
+
): Promise<any> {
|
|
111
|
+
// Acquire slots
|
|
112
|
+
for (const { group, key } of groups) {
|
|
113
|
+
group.acquireSlot(key);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
return await task.trigger(input);
|
|
118
|
+
} finally {
|
|
119
|
+
// Release slots
|
|
120
|
+
for (const { group, key } of groups) {
|
|
121
|
+
group.releaseSlot(key);
|
|
122
|
+
}
|
|
123
|
+
this.drainConstraintQueue();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private drainConstraintQueue(): void {
|
|
128
|
+
let shortestCooldown = Infinity;
|
|
129
|
+
const stillQueued: IConstrainedTaskEntry[] = [];
|
|
130
|
+
|
|
131
|
+
for (const entry of this.constraintQueue) {
|
|
132
|
+
const applicableGroups: Array<{ group: TaskConstraintGroup<any>; key: string }> = [];
|
|
133
|
+
for (const group of this.constraintGroups) {
|
|
134
|
+
const key = group.getConstraintKey(entry.task);
|
|
135
|
+
if (key !== null) {
|
|
136
|
+
applicableGroups.push({ group, key });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// No constraints apply anymore (group removed?) → run directly
|
|
141
|
+
if (applicableGroups.length === 0) {
|
|
142
|
+
entry.task.trigger(entry.input).then(
|
|
143
|
+
(result) => entry.deferred.resolve(result),
|
|
144
|
+
(err) => entry.deferred.reject(err),
|
|
145
|
+
);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const allCanRun = applicableGroups.every(({ group, key }) => group.canRun(key));
|
|
150
|
+
if (allCanRun) {
|
|
151
|
+
this.executeWithConstraintTracking(entry.task, entry.input, applicableGroups).then(
|
|
152
|
+
(result) => entry.deferred.resolve(result),
|
|
153
|
+
(err) => entry.deferred.reject(err),
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
stillQueued.push(entry);
|
|
157
|
+
// Track shortest cooldown for timer scheduling
|
|
158
|
+
for (const { group, key } of applicableGroups) {
|
|
159
|
+
const remaining = group.getCooldownRemaining(key);
|
|
160
|
+
if (remaining > 0 && remaining < shortestCooldown) {
|
|
161
|
+
shortestCooldown = remaining;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.constraintQueue = stillQueued;
|
|
168
|
+
|
|
169
|
+
// Schedule next drain if there are cooldown-blocked entries
|
|
170
|
+
if (this.drainTimer) {
|
|
171
|
+
clearTimeout(this.drainTimer);
|
|
172
|
+
this.drainTimer = null;
|
|
173
|
+
}
|
|
174
|
+
if (stillQueued.length > 0 && shortestCooldown < Infinity) {
|
|
175
|
+
this.drainTimer = setTimeout(() => {
|
|
176
|
+
this.drainTimer = null;
|
|
177
|
+
this.drainConstraintQueue();
|
|
178
|
+
}, shortestCooldown + 1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
63
182
|
public async triggerTaskByName(taskName: string): Promise<any> {
|
|
64
183
|
const taskToTrigger = this.getTaskByName(taskName);
|
|
65
184
|
if (!taskToTrigger) {
|
|
66
185
|
throw new Error(`No task with the name ${taskName} found.`);
|
|
67
186
|
}
|
|
68
|
-
return
|
|
187
|
+
return this.triggerTaskConstrained(taskToTrigger);
|
|
69
188
|
}
|
|
70
189
|
|
|
71
|
-
public async triggerTask(task: Task<any, any>) {
|
|
72
|
-
return
|
|
190
|
+
public async triggerTask(task: Task<any, any, any>) {
|
|
191
|
+
return this.triggerTaskConstrained(task);
|
|
73
192
|
}
|
|
74
193
|
|
|
75
194
|
public scheduleTaskByName(taskName: string, cronString: string) {
|
|
@@ -80,7 +199,7 @@ export class TaskManager {
|
|
|
80
199
|
this.handleTaskScheduling(taskToSchedule, cronString);
|
|
81
200
|
}
|
|
82
201
|
|
|
83
|
-
private handleTaskScheduling(task: Task<any, any>, cronString: string) {
|
|
202
|
+
private handleTaskScheduling(task: Task<any, any, any>, cronString: string) {
|
|
84
203
|
const cronJob = this.cronJobManager.addCronjob(
|
|
85
204
|
cronString,
|
|
86
205
|
async (triggerTime: number) => {
|
|
@@ -98,7 +217,7 @@ export class TaskManager {
|
|
|
98
217
|
}
|
|
99
218
|
}
|
|
100
219
|
try {
|
|
101
|
-
await
|
|
220
|
+
await this.triggerTaskConstrained(task);
|
|
102
221
|
} catch (err) {
|
|
103
222
|
logger.log('error', `TaskManager: scheduled task "${task.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
104
223
|
}
|
|
@@ -107,7 +226,7 @@ export class TaskManager {
|
|
|
107
226
|
task.cronJob = cronJob;
|
|
108
227
|
}
|
|
109
228
|
|
|
110
|
-
private logTaskState(task: Task<any, any>) {
|
|
229
|
+
private logTaskState(task: Task<any, any, any>) {
|
|
111
230
|
logger.log('info', `Taskbuffer schedule triggered task >>${task.name}<<`);
|
|
112
231
|
const bufferState = task.buffered
|
|
113
232
|
? `buffered with max ${task.bufferMax} buffered calls`
|
|
@@ -116,7 +235,7 @@ export class TaskManager {
|
|
|
116
235
|
}
|
|
117
236
|
|
|
118
237
|
private async performDistributedConsultation(
|
|
119
|
-
task: Task<any, any>,
|
|
238
|
+
task: Task<any, any, any>,
|
|
120
239
|
triggerTime: number,
|
|
121
240
|
): Promise<IDistributedTaskRequestResult> {
|
|
122
241
|
logger.log('info', 'Found a distributed coordinator, performing consultation.');
|
|
@@ -144,7 +263,7 @@ export class TaskManager {
|
|
|
144
263
|
}
|
|
145
264
|
}
|
|
146
265
|
|
|
147
|
-
public async descheduleTask(task: Task<any, any>) {
|
|
266
|
+
public async descheduleTask(task: Task<any, any, any>) {
|
|
148
267
|
await this.descheduleTaskByName(task.name);
|
|
149
268
|
}
|
|
150
269
|
|
|
@@ -169,6 +288,10 @@ export class TaskManager {
|
|
|
169
288
|
subscription.unsubscribe();
|
|
170
289
|
}
|
|
171
290
|
this.taskSubscriptions.clear();
|
|
291
|
+
if (this.drainTimer) {
|
|
292
|
+
clearTimeout(this.drainTimer);
|
|
293
|
+
this.drainTimer = null;
|
|
294
|
+
}
|
|
172
295
|
}
|
|
173
296
|
|
|
174
297
|
// Get metadata for a specific task
|
|
@@ -186,7 +309,7 @@ export class TaskManager {
|
|
|
186
309
|
// Get scheduled tasks with their schedules and next run times
|
|
187
310
|
public getScheduledTasks(): IScheduledTaskInfo[] {
|
|
188
311
|
const scheduledTasks: IScheduledTaskInfo[] = [];
|
|
189
|
-
|
|
312
|
+
|
|
190
313
|
for (const task of this.taskMap.getArray()) {
|
|
191
314
|
if (task.cronJob) {
|
|
192
315
|
scheduledTasks.push({
|
|
@@ -199,7 +322,7 @@ export class TaskManager {
|
|
|
199
322
|
});
|
|
200
323
|
}
|
|
201
324
|
}
|
|
202
|
-
|
|
325
|
+
|
|
203
326
|
return scheduledTasks;
|
|
204
327
|
}
|
|
205
328
|
|
|
@@ -213,11 +336,11 @@ export class TaskManager {
|
|
|
213
336
|
}))
|
|
214
337
|
.sort((a, b) => a.nextRun.getTime() - b.nextRun.getTime())
|
|
215
338
|
.slice(0, limit);
|
|
216
|
-
|
|
339
|
+
|
|
217
340
|
return scheduledRuns;
|
|
218
341
|
}
|
|
219
342
|
|
|
220
|
-
public getTasksByLabel(key: string, value: string): Task<any, any>[] {
|
|
343
|
+
public getTasksByLabel(key: string, value: string): Task<any, any, any>[] {
|
|
221
344
|
return this.taskMap.getArray().filter(task => task.labels[key] === value);
|
|
222
345
|
}
|
|
223
346
|
|
|
@@ -227,7 +350,7 @@ export class TaskManager {
|
|
|
227
350
|
|
|
228
351
|
// Add, execute, and remove a task while collecting metadata
|
|
229
352
|
public async addExecuteRemoveTask<T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }>>(
|
|
230
|
-
task: Task<T, TSteps>,
|
|
353
|
+
task: Task<T, TSteps, any>,
|
|
231
354
|
options?: {
|
|
232
355
|
schedule?: string;
|
|
233
356
|
trackProgress?: boolean;
|
|
@@ -235,19 +358,18 @@ export class TaskManager {
|
|
|
235
358
|
): Promise<ITaskExecutionReport> {
|
|
236
359
|
// Add task to manager
|
|
237
360
|
this.addTask(task);
|
|
238
|
-
|
|
361
|
+
|
|
239
362
|
// Optionally schedule it
|
|
240
363
|
if (options?.schedule) {
|
|
241
364
|
this.scheduleTaskByName(task.name!, options.schedule);
|
|
242
365
|
}
|
|
243
|
-
|
|
366
|
+
|
|
244
367
|
const startTime = Date.now();
|
|
245
|
-
|
|
246
|
-
|
|
368
|
+
|
|
247
369
|
try {
|
|
248
|
-
// Execute the task
|
|
249
|
-
const result = await
|
|
250
|
-
|
|
370
|
+
// Execute the task through constraints
|
|
371
|
+
const result = await this.triggerTaskConstrained(task);
|
|
372
|
+
|
|
251
373
|
// Collect execution report
|
|
252
374
|
const report: ITaskExecutionReport = {
|
|
253
375
|
taskName: task.name || 'unnamed',
|
|
@@ -261,15 +383,15 @@ export class TaskManager {
|
|
|
261
383
|
progress: task.getProgress(),
|
|
262
384
|
result,
|
|
263
385
|
};
|
|
264
|
-
|
|
386
|
+
|
|
265
387
|
// Remove task from manager
|
|
266
388
|
this.removeTask(task);
|
|
267
|
-
|
|
389
|
+
|
|
268
390
|
// Deschedule if it was scheduled
|
|
269
391
|
if (options?.schedule && task.name) {
|
|
270
392
|
this.descheduleTaskByName(task.name);
|
|
271
393
|
}
|
|
272
|
-
|
|
394
|
+
|
|
273
395
|
return report;
|
|
274
396
|
} catch (error) {
|
|
275
397
|
// Create error report
|
|
@@ -285,15 +407,15 @@ export class TaskManager {
|
|
|
285
407
|
progress: task.getProgress(),
|
|
286
408
|
error: error as Error,
|
|
287
409
|
};
|
|
288
|
-
|
|
410
|
+
|
|
289
411
|
// Remove task from manager even on error
|
|
290
412
|
this.removeTask(task);
|
|
291
|
-
|
|
413
|
+
|
|
292
414
|
// Deschedule if it was scheduled
|
|
293
415
|
if (options?.schedule && task.name) {
|
|
294
416
|
this.descheduleTaskByName(task.name);
|
|
295
417
|
}
|
|
296
|
-
|
|
418
|
+
|
|
297
419
|
throw errorReport;
|
|
298
420
|
}
|
|
299
421
|
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { ITaskStep } from './taskbuffer.classes.taskstep.js';
|
|
2
|
+
import type { Task } from './taskbuffer.classes.task.js';
|
|
3
|
+
|
|
4
|
+
export interface ITaskConstraintGroupOptions<TData extends Record<string, unknown> = Record<string, unknown>> {
|
|
5
|
+
name: string;
|
|
6
|
+
constraintKeyForTask: (task: Task<any, any, TData>) => string | null | undefined;
|
|
7
|
+
maxConcurrent?: number; // default: Infinity
|
|
8
|
+
cooldownMs?: number; // default: 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IConstrainedTaskEntry {
|
|
12
|
+
task: Task<any, any, any>;
|
|
13
|
+
input: any;
|
|
14
|
+
deferred: import('@push.rocks/smartpromise').Deferred<any>;
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
export interface ITaskMetadata {
|
|
4
18
|
name: string;
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/taskbuffer',
|
|
6
|
-
version: '
|
|
6
|
+
version: '5.0.0',
|
|
7
7
|
description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.'
|
|
8
8
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as plugins from './taskbuffer.plugins.js';
|
|
2
|
-
import { Task } from './taskbuffer.classes.task.js';
|
|
3
|
-
export declare class TaskRunner {
|
|
4
|
-
maxParallelJobs: number;
|
|
5
|
-
status: 'stopped' | 'running';
|
|
6
|
-
runningTasks: plugins.lik.ObjectMap<Task>;
|
|
7
|
-
queuedTasks: Task[];
|
|
8
|
-
constructor();
|
|
9
|
-
/**
|
|
10
|
-
* adds a task to the queue
|
|
11
|
-
*/
|
|
12
|
-
addTask(taskArg: Task): void;
|
|
13
|
-
/**
|
|
14
|
-
* set amount of parallel tasks
|
|
15
|
-
* be careful, you might lose dependability of tasks
|
|
16
|
-
*/
|
|
17
|
-
setMaxParallelJobs(maxParallelJobsArg: number): void;
|
|
18
|
-
/**
|
|
19
|
-
* starts the task queue
|
|
20
|
-
*/
|
|
21
|
-
start(): Promise<void>;
|
|
22
|
-
/**
|
|
23
|
-
* checks whether execution is on point
|
|
24
|
-
*/
|
|
25
|
-
checkExecution(): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* stops the task queue
|
|
28
|
-
*/
|
|
29
|
-
stop(): Promise<void>;
|
|
30
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import * as plugins from './taskbuffer.plugins.js';
|
|
2
|
-
import { Task } from './taskbuffer.classes.task.js';
|
|
3
|
-
import { logger } from './taskbuffer.logging.js';
|
|
4
|
-
export class TaskRunner {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.maxParallelJobs = 1;
|
|
7
|
-
this.status = 'stopped';
|
|
8
|
-
this.runningTasks = new plugins.lik.ObjectMap();
|
|
9
|
-
this.queuedTasks = [];
|
|
10
|
-
this.runningTasks.eventSubject.subscribe(async (eventArg) => {
|
|
11
|
-
this.checkExecution();
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* adds a task to the queue
|
|
16
|
-
*/
|
|
17
|
-
addTask(taskArg) {
|
|
18
|
-
this.queuedTasks.push(taskArg);
|
|
19
|
-
this.checkExecution();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* set amount of parallel tasks
|
|
23
|
-
* be careful, you might lose dependability of tasks
|
|
24
|
-
*/
|
|
25
|
-
setMaxParallelJobs(maxParallelJobsArg) {
|
|
26
|
-
this.maxParallelJobs = maxParallelJobsArg;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* starts the task queue
|
|
30
|
-
*/
|
|
31
|
-
async start() {
|
|
32
|
-
this.status = 'running';
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* checks whether execution is on point
|
|
36
|
-
*/
|
|
37
|
-
async checkExecution() {
|
|
38
|
-
if (this.runningTasks.getArray().length < this.maxParallelJobs &&
|
|
39
|
-
this.status === 'running' &&
|
|
40
|
-
this.queuedTasks.length > 0) {
|
|
41
|
-
const nextJob = this.queuedTasks.shift();
|
|
42
|
-
this.runningTasks.add(nextJob);
|
|
43
|
-
try {
|
|
44
|
-
await nextJob.trigger();
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
logger.log('error', `TaskRunner: task "${nextJob.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
48
|
-
}
|
|
49
|
-
this.runningTasks.remove(nextJob);
|
|
50
|
-
this.checkExecution();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* stops the task queue
|
|
55
|
-
*/
|
|
56
|
-
async stop() {
|
|
57
|
-
this.status = 'stopped';
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFza2J1ZmZlci5jbGFzc2VzLnRhc2tydW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90YXNrYnVmZmVyLmNsYXNzZXMudGFza3J1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFakQsTUFBTSxPQUFPLFVBQVU7SUFPckI7UUFOTyxvQkFBZSxHQUFXLENBQUMsQ0FBQztRQUM1QixXQUFNLEdBQTBCLFNBQVMsQ0FBQztRQUMxQyxpQkFBWSxHQUNqQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFRLENBQUM7UUFDN0IsZ0JBQVcsR0FBVyxFQUFFLENBQUM7UUFHOUIsSUFBSSxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsRUFBRTtZQUMxRCxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDeEIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPLENBQUMsT0FBYTtRQUMxQixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUMvQixJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7SUFDeEIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLGtCQUFrQixDQUFDLGtCQUEwQjtRQUNsRCxJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFDO0lBQzVDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBQzFCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxjQUFjO1FBQ3pCLElBQ0UsSUFBSSxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLGVBQWU7WUFDMUQsSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO1lBQ3pCLElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTSxHQUFHLENBQUMsRUFDM0IsQ0FBQztZQUNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDekMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDL0IsSUFBSSxDQUFDO2dCQUNILE1BQU0sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQzFCLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLHFCQUFxQixPQUFPLENBQUMsSUFBSSxJQUFJLFNBQVMsYUFBYSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ3JJLENBQUM7WUFDRCxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNsQyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDeEIsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxJQUFJO1FBQ2YsSUFBSSxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUM7SUFDMUIsQ0FBQztDQUNGIn0=
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import * as plugins from './taskbuffer.plugins.js';
|
|
2
|
-
|
|
3
|
-
import { Task } from './taskbuffer.classes.task.js';
|
|
4
|
-
import { logger } from './taskbuffer.logging.js';
|
|
5
|
-
|
|
6
|
-
export class TaskRunner {
|
|
7
|
-
public maxParallelJobs: number = 1;
|
|
8
|
-
public status: 'stopped' | 'running' = 'stopped';
|
|
9
|
-
public runningTasks: plugins.lik.ObjectMap<Task> =
|
|
10
|
-
new plugins.lik.ObjectMap<Task>();
|
|
11
|
-
public queuedTasks: Task[] = [];
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
this.runningTasks.eventSubject.subscribe(async (eventArg) => {
|
|
15
|
-
this.checkExecution();
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* adds a task to the queue
|
|
21
|
-
*/
|
|
22
|
-
public addTask(taskArg: Task) {
|
|
23
|
-
this.queuedTasks.push(taskArg);
|
|
24
|
-
this.checkExecution();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* set amount of parallel tasks
|
|
29
|
-
* be careful, you might lose dependability of tasks
|
|
30
|
-
*/
|
|
31
|
-
public setMaxParallelJobs(maxParallelJobsArg: number) {
|
|
32
|
-
this.maxParallelJobs = maxParallelJobsArg;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* starts the task queue
|
|
37
|
-
*/
|
|
38
|
-
public async start() {
|
|
39
|
-
this.status = 'running';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* checks whether execution is on point
|
|
44
|
-
*/
|
|
45
|
-
public async checkExecution() {
|
|
46
|
-
if (
|
|
47
|
-
this.runningTasks.getArray().length < this.maxParallelJobs &&
|
|
48
|
-
this.status === 'running' &&
|
|
49
|
-
this.queuedTasks.length > 0
|
|
50
|
-
) {
|
|
51
|
-
const nextJob = this.queuedTasks.shift();
|
|
52
|
-
this.runningTasks.add(nextJob);
|
|
53
|
-
try {
|
|
54
|
-
await nextJob.trigger();
|
|
55
|
-
} catch (err) {
|
|
56
|
-
logger.log('error', `TaskRunner: task "${nextJob.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
57
|
-
}
|
|
58
|
-
this.runningTasks.remove(nextJob);
|
|
59
|
-
this.checkExecution();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* stops the task queue
|
|
65
|
-
*/
|
|
66
|
-
public async stop() {
|
|
67
|
-
this.status = 'stopped';
|
|
68
|
-
}
|
|
69
|
-
}
|