@supergrowthai/tq 1.0.4 → 1.0.6
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/README.md +138 -0
- package/dist/{PrismaAdapter-D-Bjaonm.cjs → PrismaAdapter-CT8dxOZX.cjs} +51 -3
- package/dist/PrismaAdapter-CT8dxOZX.cjs.map +1 -0
- package/dist/{PrismaAdapter-Kp4A4VHb.js → PrismaAdapter-Z2vLslDJ.js} +51 -3
- package/dist/PrismaAdapter-Z2vLslDJ.js.map +1 -0
- package/dist/adapters/index.cjs +1 -1
- package/dist/adapters/index.mjs +1 -1
- package/dist/core/Actions.cjs +13 -0
- package/dist/core/Actions.cjs.map +1 -1
- package/dist/core/Actions.mjs +13 -0
- package/dist/core/Actions.mjs.map +1 -1
- package/dist/index.cjs +603 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +609 -134
- package/dist/index.mjs.map +1 -1
- package/dist/src/adapters/ITaskStorageAdapter.d.cts +12 -0
- package/dist/src/adapters/ITaskStorageAdapter.d.ts +12 -0
- package/dist/src/adapters/InMemoryAdapter.d.cts +26 -1
- package/dist/src/adapters/InMemoryAdapter.d.ts +26 -1
- package/dist/src/adapters/MongoDbAdapter.d.cts +29 -2
- package/dist/src/adapters/MongoDbAdapter.d.ts +29 -2
- package/dist/src/adapters/PrismaAdapter.d.cts +36 -2
- package/dist/src/adapters/PrismaAdapter.d.ts +36 -2
- package/dist/src/adapters/index.d.cts +1 -1
- package/dist/src/adapters/index.d.ts +1 -1
- package/dist/src/core/Actions.d.cts +5 -0
- package/dist/src/core/Actions.d.ts +5 -0
- package/dist/src/core/TaskHandler.d.cts +22 -2
- package/dist/src/core/TaskHandler.d.ts +22 -2
- package/dist/src/core/TaskRunner.d.cts +10 -1
- package/dist/src/core/TaskRunner.d.ts +10 -1
- package/dist/src/core/lifecycle.d.cts +133 -0
- package/dist/src/core/lifecycle.d.ts +133 -0
- package/dist/src/index.d.cts +2 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/providers/ConsoleHealthProvider.d.cts +47 -0
- package/dist/src/providers/ConsoleHealthProvider.d.ts +47 -0
- package/dist/src/providers/index.d.cts +1 -0
- package/dist/src/providers/index.d.ts +1 -0
- package/dist/src/utils/disposable-lock.d.cts +39 -0
- package/dist/src/utils/disposable-lock.d.ts +39 -0
- package/package.json +7 -7
- package/dist/PrismaAdapter-D-Bjaonm.cjs.map +0 -1
- package/dist/PrismaAdapter-Kp4A4VHb.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
8
|
+
var dispose, inner;
|
|
9
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
dispose = value[__knownSymbol("dispose")];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
15
|
+
if (inner) dispose = function() {
|
|
16
|
+
try {
|
|
17
|
+
inner.call(this);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return Promise.reject(e);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
stack.push([async, dispose, value]);
|
|
23
|
+
} else if (async) {
|
|
24
|
+
stack.push([async]);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __callDispose = (stack, error, hasError) => {
|
|
29
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
30
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
31
|
+
};
|
|
32
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
33
|
+
var next = (it) => {
|
|
34
|
+
while (it = stack.pop()) {
|
|
35
|
+
try {
|
|
36
|
+
var result = it[1] && it[1].call(it[2]);
|
|
37
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hasError) throw error;
|
|
43
|
+
};
|
|
44
|
+
return next();
|
|
45
|
+
};
|
|
46
|
+
import { I, M, P } from "./PrismaAdapter-Z2vLslDJ.js";
|
|
2
47
|
import { getEnvironmentQueueName } from "@supergrowthai/mq";
|
|
3
48
|
import { L as Logger, a as LogLevel } from "./client-BAiCkZv7.js";
|
|
4
49
|
import { tId } from "./utils/task-id-gen.mjs";
|
|
@@ -256,8 +301,8 @@ function require_Symbol() {
|
|
|
256
301
|
if (hasRequired_Symbol) return _Symbol;
|
|
257
302
|
hasRequired_Symbol = 1;
|
|
258
303
|
var root = require_root();
|
|
259
|
-
var
|
|
260
|
-
_Symbol =
|
|
304
|
+
var Symbol2 = root.Symbol;
|
|
305
|
+
_Symbol = Symbol2;
|
|
261
306
|
return _Symbol;
|
|
262
307
|
}
|
|
263
308
|
var _getRawTag;
|
|
@@ -265,11 +310,11 @@ var hasRequired_getRawTag;
|
|
|
265
310
|
function require_getRawTag() {
|
|
266
311
|
if (hasRequired_getRawTag) return _getRawTag;
|
|
267
312
|
hasRequired_getRawTag = 1;
|
|
268
|
-
var
|
|
313
|
+
var Symbol2 = require_Symbol();
|
|
269
314
|
var objectProto = Object.prototype;
|
|
270
315
|
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
271
316
|
var nativeObjectToString = objectProto.toString;
|
|
272
|
-
var symToStringTag =
|
|
317
|
+
var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
|
|
273
318
|
function getRawTag(value) {
|
|
274
319
|
var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
|
|
275
320
|
try {
|
|
@@ -308,9 +353,9 @@ var hasRequired_baseGetTag;
|
|
|
308
353
|
function require_baseGetTag() {
|
|
309
354
|
if (hasRequired_baseGetTag) return _baseGetTag;
|
|
310
355
|
hasRequired_baseGetTag = 1;
|
|
311
|
-
var
|
|
356
|
+
var Symbol2 = require_Symbol(), getRawTag = require_getRawTag(), objectToString = require_objectToString();
|
|
312
357
|
var nullTag = "[object Null]", undefinedTag = "[object Undefined]";
|
|
313
|
-
var symToStringTag =
|
|
358
|
+
var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
|
|
314
359
|
function baseGetTag(value) {
|
|
315
360
|
if (value == null) {
|
|
316
361
|
return value === void 0 ? undefinedTag : nullTag;
|
|
@@ -553,144 +598,326 @@ function requireChunk() {
|
|
|
553
598
|
}
|
|
554
599
|
var chunkExports = requireChunk();
|
|
555
600
|
const chunk = /* @__PURE__ */ getDefaultExportFromCjs(chunkExports);
|
|
601
|
+
class DisposableLockBatch {
|
|
602
|
+
constructor(lockManager, onError) {
|
|
603
|
+
this.lockManager = lockManager;
|
|
604
|
+
this.onError = onError;
|
|
605
|
+
this.lockIds = [];
|
|
606
|
+
this.disposed = false;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get the list of currently held lock IDs
|
|
610
|
+
*/
|
|
611
|
+
get heldLocks() {
|
|
612
|
+
return this.lockIds;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Number of locks currently held
|
|
616
|
+
*/
|
|
617
|
+
get size() {
|
|
618
|
+
return this.lockIds.length;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Acquire a lock and track it for automatic release
|
|
622
|
+
*/
|
|
623
|
+
async acquire(lockId, timeout) {
|
|
624
|
+
if (this.disposed) {
|
|
625
|
+
throw new Error("Cannot acquire lock on disposed DisposableLockBatch");
|
|
626
|
+
}
|
|
627
|
+
const acquired = await this.lockManager.acquire(lockId, timeout);
|
|
628
|
+
if (acquired) {
|
|
629
|
+
this.lockIds.push(lockId);
|
|
630
|
+
}
|
|
631
|
+
return acquired;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* AsyncDisposable implementation - releases all locks
|
|
635
|
+
*/
|
|
636
|
+
async [Symbol.asyncDispose]() {
|
|
637
|
+
if (this.disposed) return;
|
|
638
|
+
this.disposed = true;
|
|
639
|
+
await Promise.all(
|
|
640
|
+
this.lockIds.map(
|
|
641
|
+
(lockId) => this.lockManager.release(lockId).catch((err) => {
|
|
642
|
+
if (this.onError) {
|
|
643
|
+
this.onError(lockId, err);
|
|
644
|
+
}
|
|
645
|
+
})
|
|
646
|
+
)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
556
650
|
class TaskRunner {
|
|
557
|
-
constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId) {
|
|
651
|
+
constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId, lifecycleProvider, lifecycleConfig) {
|
|
558
652
|
this.messageQueue = messageQueue;
|
|
559
653
|
this.taskQueue = taskQueue;
|
|
560
654
|
this.taskStore = taskStore;
|
|
561
655
|
this.generateId = generateId;
|
|
656
|
+
this.lifecycleProvider = lifecycleProvider;
|
|
657
|
+
this.lifecycleConfig = lifecycleConfig;
|
|
658
|
+
this.taskStartTimes = /* @__PURE__ */ new Map();
|
|
562
659
|
this.logger = new Logger("TaskRunner", LogLevel.INFO);
|
|
563
660
|
this.lockManager = new LockManager(cacheProvider, {
|
|
564
661
|
prefix: "task_lock_",
|
|
565
662
|
defaultTimeout: 30 * 60
|
|
566
663
|
});
|
|
567
664
|
}
|
|
665
|
+
// ============ Lifecycle Helpers ============
|
|
568
666
|
async run(taskRunnerId, tasksRaw, asyncTaskManager, abortSignal) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
this.logger.info(`[${taskRunnerId}]
|
|
573
|
-
return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [] };
|
|
574
|
-
}
|
|
575
|
-
const tasks = await this.lockManager.filterLocked(tasksRaw, tId);
|
|
576
|
-
this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
|
|
577
|
-
await Promise.all(tasks.map((t) => this.lockManager.acquire(tId(t))));
|
|
578
|
-
const groupedTasksObject = tasks.reduce((acc, task) => {
|
|
579
|
-
acc[task.type] = acc[task.type] || [];
|
|
580
|
-
acc[task.type].push(task);
|
|
581
|
-
return acc;
|
|
582
|
-
}, {});
|
|
583
|
-
const groupedTasksArray = Object.keys(groupedTasksObject).reduce((acc, type) => {
|
|
584
|
-
acc.push({ type, tasks: groupedTasksObject[type] });
|
|
585
|
-
return acc;
|
|
586
|
-
}, []);
|
|
587
|
-
this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
|
|
588
|
-
const actions = new Actions(taskRunnerId);
|
|
589
|
-
const asyncTasks = [];
|
|
590
|
-
const processedTaskIds = /* @__PURE__ */ new Set();
|
|
591
|
-
for (let i = 0; i < groupedTasksArray.length; i++) {
|
|
667
|
+
var _stack = [];
|
|
668
|
+
try {
|
|
669
|
+
this.logger.info(`[${taskRunnerId}] Starting task runner`);
|
|
670
|
+
this.logger.info(`[${taskRunnerId}] Processing ${tasksRaw.length} provided tasks`);
|
|
592
671
|
if (abortSignal?.aborted) {
|
|
593
|
-
this.logger.info(`[${taskRunnerId}] AbortSignal
|
|
594
|
-
|
|
672
|
+
this.logger.info(`[${taskRunnerId}] AbortSignal already aborted, returning empty results`);
|
|
673
|
+
return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [] };
|
|
595
674
|
}
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
this.
|
|
600
|
-
|
|
675
|
+
const tasks = await this.lockManager.filterLocked(tasksRaw, tId);
|
|
676
|
+
this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
|
|
677
|
+
const locks = __using(_stack, new DisposableLockBatch(
|
|
678
|
+
this.lockManager,
|
|
679
|
+
(lockId, err) => this.logger.error(`[${taskRunnerId}] Failed to release lock ${lockId}:`, err)
|
|
680
|
+
), true);
|
|
681
|
+
for (const task of tasks) {
|
|
682
|
+
await locks.acquire(tId(task));
|
|
601
683
|
}
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
684
|
+
const groupedTasksObject = tasks.reduce((acc, task) => {
|
|
685
|
+
acc[task.type] = acc[task.type] || [];
|
|
686
|
+
acc[task.type].push(task);
|
|
687
|
+
return acc;
|
|
688
|
+
}, {});
|
|
689
|
+
const groupedTasksArray = Object.keys(groupedTasksObject).reduce((acc, type) => {
|
|
690
|
+
acc.push({ type, tasks: groupedTasksObject[type] });
|
|
691
|
+
return acc;
|
|
692
|
+
}, []);
|
|
693
|
+
this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
|
|
694
|
+
const actions = new Actions(taskRunnerId);
|
|
695
|
+
const asyncTasks = [];
|
|
696
|
+
const processedTaskIds = /* @__PURE__ */ new Set();
|
|
697
|
+
for (let i = 0; i < groupedTasksArray.length; i++) {
|
|
698
|
+
if (abortSignal?.aborted) {
|
|
699
|
+
this.logger.info(`[${taskRunnerId}] AbortSignal detected, stopping task group processing`);
|
|
700
|
+
break;
|
|
608
701
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
taskGroup.tasks.forEach((task) => processedTaskIds.add(tId(task)));
|
|
622
|
-
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
623
|
-
if (executor.multiple) {
|
|
624
|
-
await executor.onTasks(taskGroup.tasks, actions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`));
|
|
625
|
-
} else {
|
|
626
|
-
if (executor.parallel) {
|
|
627
|
-
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
628
|
-
this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
|
|
629
|
-
for (const chunk2 of chunks) {
|
|
630
|
-
const chunkTasks = [];
|
|
631
|
-
for (let j = 0; j < chunk2.length; j++) {
|
|
632
|
-
const taskActions = actions.forkForTask(chunk2[j]);
|
|
633
|
-
chunkTasks.push(executor.onTask(chunk2[j], taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`)));
|
|
634
|
-
}
|
|
635
|
-
await Promise.all(chunkTasks);
|
|
702
|
+
const taskGroup = groupedTasksArray[i];
|
|
703
|
+
const firstTask = taskGroup.tasks[0];
|
|
704
|
+
if (!firstTask) {
|
|
705
|
+
this.logger.warn(`[${taskRunnerId}] No tasks found for type: ${taskGroup.type}`);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
const executor = this.taskQueue.getExecutor(firstTask.queue_id, taskGroup.type);
|
|
709
|
+
if (!executor) {
|
|
710
|
+
this.logger.warn(`[${taskRunnerId}] No executor found for type: ${taskGroup.type} in queue ${firstTask.queue_id}`);
|
|
711
|
+
for (const task of taskGroup.tasks) {
|
|
712
|
+
const taskWithId = task.id ? task : { ...task, id: this.generateId() };
|
|
713
|
+
actions.addIgnoredTask(taskWithId);
|
|
636
714
|
}
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (executor.asyncConfig?.handoffTimeout && asyncTaskManager && !asyncTaskManager.canAcceptTask()) {
|
|
718
|
+
this.logger.warn(`[${taskRunnerId}] Async queue full, rescheduling ${taskGroup.tasks.length} ${taskGroup.type} tasks for 3 min later`);
|
|
719
|
+
const rescheduledTasks = taskGroup.tasks.map((task) => ({
|
|
720
|
+
...task,
|
|
721
|
+
execute_at: new Date(Date.now() + 18e4),
|
|
722
|
+
status: "scheduled"
|
|
723
|
+
}));
|
|
724
|
+
await this.taskStore.updateTasksForRetry(rescheduledTasks);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
taskGroup.tasks.forEach((task) => processedTaskIds.add(tId(task)));
|
|
728
|
+
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
729
|
+
if (executor.multiple) {
|
|
730
|
+
await executor.onTasks(taskGroup.tasks, actions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`));
|
|
637
731
|
} else {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
732
|
+
if (executor.parallel) {
|
|
733
|
+
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
734
|
+
this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
|
|
735
|
+
for (const taskChunk of chunks) {
|
|
736
|
+
for (const task of taskChunk) {
|
|
737
|
+
this.emitTaskStarted(task, taskRunnerId);
|
|
738
|
+
}
|
|
739
|
+
const chunkPromises = [];
|
|
740
|
+
for (let j = 0; j < taskChunk.length; j++) {
|
|
741
|
+
const taskActions = actions.forkForTask(taskChunk[j]);
|
|
742
|
+
chunkPromises.push(executor.onTask(taskChunk[j], taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`)));
|
|
743
|
+
}
|
|
744
|
+
await Promise.all(chunkPromises);
|
|
745
|
+
for (const task of taskChunk) {
|
|
746
|
+
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
747
|
+
if (resultStatus === "success") {
|
|
748
|
+
this.emitTaskCompleted(task, taskRunnerId);
|
|
749
|
+
} else if (resultStatus === "fail") {
|
|
750
|
+
const retryCount = task.execution_stats?.retry_count || 0;
|
|
751
|
+
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
752
|
+
const willRetry = retryCount < maxRetries;
|
|
753
|
+
this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
|
|
754
|
+
}
|
|
659
755
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
const timeoutMs = executor.asyncConfig?.handoffTimeout;
|
|
759
|
+
for (let j = 0; j < taskGroup.tasks.length; j++) {
|
|
760
|
+
const task = taskGroup.tasks[j];
|
|
761
|
+
if (!timeoutMs) {
|
|
762
|
+
this.emitTaskStarted(task, taskRunnerId);
|
|
763
|
+
const taskActions = actions.forkForTask(task);
|
|
764
|
+
await executor.onTask(task, taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`));
|
|
765
|
+
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
766
|
+
if (resultStatus === "success") {
|
|
767
|
+
this.emitTaskCompleted(task, taskRunnerId);
|
|
768
|
+
} else if (resultStatus === "fail") {
|
|
769
|
+
const retryCount = task.execution_stats?.retry_count || 0;
|
|
770
|
+
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
771
|
+
const willRetry = retryCount < maxRetries;
|
|
772
|
+
this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
this.emitTaskStarted(task, taskRunnerId);
|
|
776
|
+
const startTime = Date.now();
|
|
777
|
+
const taskActions = actions.forkForTask(task);
|
|
778
|
+
const taskPromise = executor.onTask(task, taskActions).catch((err) => {
|
|
779
|
+
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
780
|
+
});
|
|
781
|
+
let timeoutId;
|
|
782
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
783
|
+
timeoutId = setTimeout(() => {
|
|
784
|
+
resolve("~~~timeout");
|
|
785
|
+
}, timeoutMs);
|
|
786
|
+
});
|
|
787
|
+
const result = await Promise.race([taskPromise, timeoutPromise]);
|
|
788
|
+
if (timeoutId) {
|
|
789
|
+
clearTimeout(timeoutId);
|
|
664
790
|
}
|
|
665
|
-
if (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
791
|
+
if (result !== "~~~timeout") {
|
|
792
|
+
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
793
|
+
if (resultStatus === "success") {
|
|
794
|
+
this.emitTaskCompleted(task, taskRunnerId);
|
|
795
|
+
} else if (resultStatus === "fail") {
|
|
796
|
+
const retryCount = task.execution_stats?.retry_count || 0;
|
|
797
|
+
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
798
|
+
const willRetry = retryCount < maxRetries;
|
|
799
|
+
this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (result === "~~~timeout") {
|
|
803
|
+
this.logger.info(`[${taskRunnerId}] Task ${tId(task)} (${task.type}) exceeded ${timeoutMs}ms, marking for async handoff`);
|
|
804
|
+
if (!asyncTaskManager) {
|
|
805
|
+
throw new Error(`Task ${task.type} exceeded timeout but AsyncTaskManager not initialized!`);
|
|
806
|
+
}
|
|
807
|
+
if (!task.id) {
|
|
808
|
+
this.logger.error(`[${taskRunnerId}] Cannot hand off task without id (type: ${task.type}). Task will continue but won't be tracked.`);
|
|
809
|
+
} else {
|
|
810
|
+
const asyncActions = new AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId);
|
|
811
|
+
const asyncPromise = taskPromise.finally(async () => {
|
|
812
|
+
try {
|
|
813
|
+
await asyncActions.onPromiseFulfilled();
|
|
814
|
+
} catch (err) {
|
|
815
|
+
this.logger.error(`[${taskRunnerId}] Failed to execute async actions for task ${tId(task)}:`, err);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
asyncTasks.push({
|
|
819
|
+
task,
|
|
820
|
+
promise: asyncPromise,
|
|
821
|
+
startTime,
|
|
822
|
+
actions: asyncActions
|
|
823
|
+
});
|
|
824
|
+
}
|
|
682
825
|
}
|
|
683
826
|
}
|
|
684
827
|
}
|
|
685
828
|
}
|
|
686
829
|
}
|
|
687
830
|
}
|
|
831
|
+
const asyncTaskIds = asyncTasks.map((at) => tId(at.task));
|
|
832
|
+
const results = actions.extractSyncResults(asyncTaskIds);
|
|
833
|
+
this.logger.info(`[${taskRunnerId}] Completing run - Success: ${results.successTasks.length}, Failed: ${results.failedTasks.length}, New: ${results.newTasks.length}, Async: ${asyncTasks.length}, Ignored: ${results.ignoredTasks.length}`);
|
|
834
|
+
return { ...results, asyncTasks };
|
|
835
|
+
} catch (_) {
|
|
836
|
+
var _error = _, _hasError = true;
|
|
837
|
+
} finally {
|
|
838
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
839
|
+
_promise && await _promise;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
emitLifecycleEvent(callback, ctx) {
|
|
843
|
+
if (!callback) return;
|
|
844
|
+
try {
|
|
845
|
+
const result = callback(ctx);
|
|
846
|
+
if (result instanceof Promise) {
|
|
847
|
+
result.catch((err) => {
|
|
848
|
+
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
} catch (err) {
|
|
852
|
+
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
buildTaskContext(task, workerId) {
|
|
856
|
+
const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
857
|
+
const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
|
|
858
|
+
const maxRetries = task.retries ?? executor?.default_retries ?? 0;
|
|
859
|
+
const payload = task.payload;
|
|
860
|
+
return {
|
|
861
|
+
task_id: task.id?.toString() || tId(task),
|
|
862
|
+
task_hash: payload?.task_hash,
|
|
863
|
+
task_type: task.type,
|
|
864
|
+
queue_id: task.queue_id,
|
|
865
|
+
payload: this.lifecycleConfig?.include_payload ? payload : {},
|
|
866
|
+
attempt: retryCount + 1,
|
|
867
|
+
max_retries: maxRetries,
|
|
868
|
+
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
869
|
+
worker_id: workerId
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
emitTaskStarted(task, workerId) {
|
|
873
|
+
const startedAt = Date.now();
|
|
874
|
+
this.taskStartTimes.set(tId(task), startedAt);
|
|
875
|
+
if (this.lifecycleProvider?.onTaskStarted) {
|
|
876
|
+
const ctx = this.buildTaskContext(task, workerId);
|
|
877
|
+
const queuedDuration = startedAt - (task.created_at?.getTime() || startedAt);
|
|
878
|
+
this.emitLifecycleEvent(
|
|
879
|
+
this.lifecycleProvider.onTaskStarted,
|
|
880
|
+
{
|
|
881
|
+
...ctx,
|
|
882
|
+
started_at: new Date(startedAt),
|
|
883
|
+
queued_duration_ms: queuedDuration
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
emitTaskCompleted(task, workerId, result) {
|
|
889
|
+
const completedAt = Date.now();
|
|
890
|
+
const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
|
|
891
|
+
this.taskStartTimes.delete(tId(task));
|
|
892
|
+
if (this.lifecycleProvider?.onTaskCompleted) {
|
|
893
|
+
const ctx = this.buildTaskContext(task, workerId);
|
|
894
|
+
const timing = {
|
|
895
|
+
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
896
|
+
processing_duration_ms: completedAt - startedAt,
|
|
897
|
+
total_duration_ms: completedAt - (task.created_at?.getTime() || completedAt)
|
|
898
|
+
};
|
|
899
|
+
this.emitLifecycleEvent(
|
|
900
|
+
this.lifecycleProvider.onTaskCompleted,
|
|
901
|
+
{ ...ctx, timing, result }
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt) {
|
|
906
|
+
const completedAt = Date.now();
|
|
907
|
+
const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
|
|
908
|
+
this.taskStartTimes.delete(tId(task));
|
|
909
|
+
if (this.lifecycleProvider?.onTaskFailed) {
|
|
910
|
+
const ctx = this.buildTaskContext(task, workerId);
|
|
911
|
+
const timing = {
|
|
912
|
+
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
913
|
+
processing_duration_ms: completedAt - startedAt,
|
|
914
|
+
total_duration_ms: completedAt - (task.created_at?.getTime() || completedAt)
|
|
915
|
+
};
|
|
916
|
+
this.emitLifecycleEvent(
|
|
917
|
+
this.lifecycleProvider.onTaskFailed,
|
|
918
|
+
{ ...ctx, timing, error, will_retry: willRetry, next_attempt_at: nextAttemptAt }
|
|
919
|
+
);
|
|
688
920
|
}
|
|
689
|
-
const asyncTaskIds = asyncTasks.map((at) => tId(at.task));
|
|
690
|
-
const results = actions.extractSyncResults(asyncTaskIds);
|
|
691
|
-
this.logger.info(`[${taskRunnerId}] Completing run - Success: ${results.successTasks.length}, Failed: ${results.failedTasks.length}, New: ${results.newTasks.length}, Async: ${asyncTasks.length}, Ignored: ${results.ignoredTasks.length}`);
|
|
692
|
-
await Promise.all(tasks.filter((t) => processedTaskIds.has(tId(t))).map((t) => this.lockManager.release(tId(t))));
|
|
693
|
-
return { ...results, asyncTasks };
|
|
694
921
|
}
|
|
695
922
|
}
|
|
696
923
|
function getEnabledQueues() {
|
|
@@ -704,7 +931,7 @@ const STATS_THRESHOLD = parseInt(process.env.TQ_STATS_THRESHOLD || "1000");
|
|
|
704
931
|
const FAILURE_THRESHOLD = parseInt(process.env.TQ_STATS_FAILURE_THRESHOLD || "100");
|
|
705
932
|
const INSTANCE_ID = process.env.INSTANCE_ID || "unknown";
|
|
706
933
|
class TaskHandler {
|
|
707
|
-
constructor(messageQueue, taskQueuesManager, databaseAdapter, cacheAdapter, asyncTaskManager, notificationProvider) {
|
|
934
|
+
constructor(messageQueue, taskQueuesManager, databaseAdapter, cacheAdapter, asyncTaskManager, notificationProvider, config) {
|
|
708
935
|
this.messageQueue = messageQueue;
|
|
709
936
|
this.taskQueuesManager = taskQueuesManager;
|
|
710
937
|
this.databaseAdapter = databaseAdapter;
|
|
@@ -712,10 +939,39 @@ class TaskHandler {
|
|
|
712
939
|
this.asyncTaskManager = asyncTaskManager;
|
|
713
940
|
this.notificationProvider = notificationProvider;
|
|
714
941
|
this.matureTaskTimer = null;
|
|
942
|
+
this.heartbeatTimer = null;
|
|
943
|
+
this.enabledQueues = [];
|
|
944
|
+
this.workerStarted = false;
|
|
945
|
+
this.workerStats = {
|
|
946
|
+
tasks_processed: 0,
|
|
947
|
+
tasks_succeeded: 0,
|
|
948
|
+
tasks_failed: 0,
|
|
949
|
+
avg_processing_ms: 0,
|
|
950
|
+
current_task: void 0
|
|
951
|
+
};
|
|
952
|
+
this.totalProcessingMs = 0;
|
|
715
953
|
this.queueStats = /* @__PURE__ */ new Map();
|
|
716
954
|
this.logger = new Logger("TaskHandler", LogLevel.INFO);
|
|
955
|
+
this.config = config || {};
|
|
956
|
+
this.workerId = `${(void 0)()}-${process.pid}-${Date.now()}`;
|
|
957
|
+
this.workerStartedAt = /* @__PURE__ */ new Date();
|
|
717
958
|
this.taskStore = new TaskStore(databaseAdapter);
|
|
718
|
-
this.taskRunner = new TaskRunner(
|
|
959
|
+
this.taskRunner = new TaskRunner(
|
|
960
|
+
messageQueue,
|
|
961
|
+
taskQueuesManager,
|
|
962
|
+
this.taskStore,
|
|
963
|
+
this.cacheAdapter,
|
|
964
|
+
databaseAdapter.generateId.bind(databaseAdapter),
|
|
965
|
+
this.config.lifecycleProvider,
|
|
966
|
+
this.config.lifecycle
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
// ============ Lifecycle Event Helpers ============
|
|
970
|
+
get lifecycleProvider() {
|
|
971
|
+
return this.config.lifecycleProvider;
|
|
972
|
+
}
|
|
973
|
+
get workerProvider() {
|
|
974
|
+
return this.config.workerProvider;
|
|
719
975
|
}
|
|
720
976
|
async addTasks(tasks) {
|
|
721
977
|
const diffedItems = tasks.reduce(
|
|
@@ -741,24 +997,40 @@ class TaskHandler {
|
|
|
741
997
|
const iQueues = Object.keys(diffedItems.immediate);
|
|
742
998
|
for (let i = 0; i < iQueues.length; i++) {
|
|
743
999
|
const queue = iQueues[i];
|
|
744
|
-
const
|
|
1000
|
+
const queueTasks = diffedItems.immediate[queue].map((task) => {
|
|
745
1001
|
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
746
1002
|
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
747
1003
|
const id = shouldStoreOnFailure ? { id: this.databaseAdapter.generateId() } : {};
|
|
748
1004
|
return { ...id, ...task };
|
|
749
1005
|
});
|
|
750
|
-
await this.messageQueue.addMessages(queue,
|
|
1006
|
+
await this.messageQueue.addMessages(queue, queueTasks);
|
|
1007
|
+
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
1008
|
+
for (const task of queueTasks) {
|
|
1009
|
+
this.emitLifecycleEvent(
|
|
1010
|
+
this.lifecycleProvider.onTaskScheduled,
|
|
1011
|
+
this.buildTaskContext(task)
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
751
1015
|
}
|
|
752
1016
|
const fQueues = Object.keys(diffedItems.future);
|
|
753
1017
|
for (let i = 0; i < fQueues.length; i++) {
|
|
754
1018
|
const queue = fQueues[i];
|
|
755
|
-
const
|
|
1019
|
+
const queueTasks = diffedItems.future[queue].map((task) => {
|
|
756
1020
|
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
757
1021
|
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
758
1022
|
const id = shouldStoreOnFailure ? { id: this.databaseAdapter.generateId() } : {};
|
|
759
1023
|
return { ...id, ...task };
|
|
760
1024
|
});
|
|
761
|
-
await this.taskStore.addTasksToScheduled(
|
|
1025
|
+
await this.taskStore.addTasksToScheduled(queueTasks);
|
|
1026
|
+
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
1027
|
+
for (const task of queueTasks) {
|
|
1028
|
+
this.emitLifecycleEvent(
|
|
1029
|
+
this.lifecycleProvider.onTaskScheduled,
|
|
1030
|
+
this.buildTaskContext(task)
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
762
1034
|
}
|
|
763
1035
|
}
|
|
764
1036
|
async postProcessTasks({
|
|
@@ -769,10 +1041,12 @@ class TaskHandler {
|
|
|
769
1041
|
const tasksToRetry = [];
|
|
770
1042
|
const finalFailedTasks = [];
|
|
771
1043
|
let discardedTasksCount = 0;
|
|
1044
|
+
const MAX_RETRY_DELAY_MS = 5 * 60 * 1e3;
|
|
772
1045
|
for (const task of failedTasksRaw) {
|
|
773
1046
|
const taskRetryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
774
1047
|
const taskRetryAfter = task.retry_after || 2e3;
|
|
775
|
-
const
|
|
1048
|
+
const calculatedDelay = taskRetryAfter * Math.pow(taskRetryCount + 1, 2);
|
|
1049
|
+
const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);
|
|
776
1050
|
const executeAt = Date.now() + retryAfter;
|
|
777
1051
|
const maxRetries = this.getRetryCount(task);
|
|
778
1052
|
if (task.id && taskRetryCount < maxRetries) {
|
|
@@ -814,6 +1088,23 @@ class TaskHandler {
|
|
|
814
1088
|
} else {
|
|
815
1089
|
discardedTasksCount++;
|
|
816
1090
|
this.logger.info(`Discarding task of type ${task.type} after ${taskRetryCount} retries`);
|
|
1091
|
+
if (this.lifecycleProvider?.onTaskExhausted) {
|
|
1092
|
+
const ctx = this.buildTaskContext(task);
|
|
1093
|
+
const errorMessage = task.execution_stats?.last_error || "Task exhausted all retries";
|
|
1094
|
+
this.emitLifecycleEvent(
|
|
1095
|
+
this.lifecycleProvider.onTaskExhausted,
|
|
1096
|
+
{
|
|
1097
|
+
...ctx,
|
|
1098
|
+
timing: {
|
|
1099
|
+
queued_duration_ms: 0,
|
|
1100
|
+
processing_duration_ms: 0,
|
|
1101
|
+
total_duration_ms: Date.now() - (task.created_at?.getTime() || Date.now())
|
|
1102
|
+
},
|
|
1103
|
+
error: new Error(errorMessage),
|
|
1104
|
+
total_attempts: taskRetryCount + 1
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
817
1108
|
}
|
|
818
1109
|
}
|
|
819
1110
|
if (discardedTasksCount > 0) {
|
|
@@ -838,6 +1129,18 @@ class TaskHandler {
|
|
|
838
1129
|
this.logger.info(`AbortSignal detected, skipping processing of ${tasks.length} tasks for stream ${streamName}`);
|
|
839
1130
|
return { failedTasks: [], newTasks: [], successTasks: [], asyncTasks: [], ignoredTasks: [] };
|
|
840
1131
|
}
|
|
1132
|
+
const batchStartTime = Date.now();
|
|
1133
|
+
const taskTypes = [...new Set(tasks.map((t) => t.type))];
|
|
1134
|
+
if (this.workerProvider?.onBatchStarted) {
|
|
1135
|
+
this.emitLifecycleEvent(
|
|
1136
|
+
this.workerProvider.onBatchStarted,
|
|
1137
|
+
{
|
|
1138
|
+
...this.buildWorkerInfo(),
|
|
1139
|
+
batch_size: tasks.length,
|
|
1140
|
+
task_types: taskTypes
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
841
1144
|
this.logger.debug(`Processing ${tasks.length} tasks for stream ${streamName}`);
|
|
842
1145
|
const {
|
|
843
1146
|
failedTasks,
|
|
@@ -888,10 +1191,118 @@ class TaskHandler {
|
|
|
888
1191
|
stats.async += asyncTasks.length;
|
|
889
1192
|
stats.ignored += ignoredTasks.length;
|
|
890
1193
|
await this.reportQueueStats(streamName);
|
|
1194
|
+
const batchDuration = Date.now() - batchStartTime;
|
|
1195
|
+
this.updateWorkerStats(successTasks.length, failedTasks.length, batchDuration);
|
|
1196
|
+
if (this.workerProvider?.onBatchCompleted) {
|
|
1197
|
+
this.emitLifecycleEvent(
|
|
1198
|
+
this.workerProvider.onBatchCompleted,
|
|
1199
|
+
{
|
|
1200
|
+
...this.buildWorkerInfo(),
|
|
1201
|
+
batch_size: tasks.length,
|
|
1202
|
+
succeeded: successTasks.length,
|
|
1203
|
+
failed: failedTasks.length,
|
|
1204
|
+
duration_ms: batchDuration
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
891
1208
|
this.logger.debug(`Completed processing for stream ${streamName}: ${successTasks.length} succeeded, ${failedTasks.length} failed, ${newTasks.length} new tasks, ${ignoredTasks.length} ignored`);
|
|
892
1209
|
return { failedTasks, newTasks, successTasks, asyncTasks, ignoredTasks };
|
|
893
1210
|
}, abortSignal);
|
|
894
1211
|
}
|
|
1212
|
+
taskProcessServer(abortSignal) {
|
|
1213
|
+
const queues = getEnabledQueues();
|
|
1214
|
+
this.enabledQueues = queues;
|
|
1215
|
+
if (!this.workerStarted) {
|
|
1216
|
+
this.workerStarted = true;
|
|
1217
|
+
this.emitWorkerStarted();
|
|
1218
|
+
this.startHeartbeat();
|
|
1219
|
+
}
|
|
1220
|
+
for (let i = 0; i < queues.length; i++) {
|
|
1221
|
+
this.logger.info(`Starting consumer for queue: ${queues[i]}`);
|
|
1222
|
+
this.startConsumingTasks(queues[i], abortSignal);
|
|
1223
|
+
}
|
|
1224
|
+
this.logger.info("Starting mature tasks processor");
|
|
1225
|
+
this.processMatureTasks(abortSignal);
|
|
1226
|
+
abortSignal?.addEventListener("abort", () => {
|
|
1227
|
+
this.stopHeartbeat();
|
|
1228
|
+
this.emitWorkerStopped("shutdown");
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
buildWorkerInfo() {
|
|
1232
|
+
return {
|
|
1233
|
+
worker_id: this.workerId,
|
|
1234
|
+
hostname: (void 0)(),
|
|
1235
|
+
pid: process.pid,
|
|
1236
|
+
started_at: this.workerStartedAt,
|
|
1237
|
+
enabled_queues: this.enabledQueues
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
emitWorkerStarted() {
|
|
1241
|
+
if (!this.workerProvider?.onWorkerStarted) return;
|
|
1242
|
+
this.emitLifecycleEvent(this.workerProvider.onWorkerStarted, this.buildWorkerInfo());
|
|
1243
|
+
}
|
|
1244
|
+
emitWorkerStopped(reason) {
|
|
1245
|
+
if (!this.workerProvider?.onWorkerStopped) return;
|
|
1246
|
+
this.emitLifecycleEvent(
|
|
1247
|
+
this.workerProvider.onWorkerStopped,
|
|
1248
|
+
{
|
|
1249
|
+
...this.buildWorkerInfo(),
|
|
1250
|
+
reason,
|
|
1251
|
+
final_stats: { ...this.workerStats }
|
|
1252
|
+
}
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
emitWorkerHeartbeat() {
|
|
1256
|
+
if (!this.workerProvider?.onWorkerHeartbeat) return;
|
|
1257
|
+
const memUsage = process.memoryUsage();
|
|
1258
|
+
this.emitLifecycleEvent(
|
|
1259
|
+
this.workerProvider.onWorkerHeartbeat,
|
|
1260
|
+
{
|
|
1261
|
+
...this.buildWorkerInfo(),
|
|
1262
|
+
stats: { ...this.workerStats },
|
|
1263
|
+
memory_usage_mb: memUsage.heapUsed / 1024 / 1024
|
|
1264
|
+
}
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
startHeartbeat() {
|
|
1268
|
+
if (this.heartbeatTimer) return;
|
|
1269
|
+
const intervalMs = this.config.lifecycle?.heartbeat_interval_ms || 5e3;
|
|
1270
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1271
|
+
this.emitWorkerHeartbeat();
|
|
1272
|
+
}, intervalMs);
|
|
1273
|
+
}
|
|
1274
|
+
stopHeartbeat() {
|
|
1275
|
+
if (this.heartbeatTimer) {
|
|
1276
|
+
clearInterval(this.heartbeatTimer);
|
|
1277
|
+
this.heartbeatTimer = null;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
updateWorkerStats(succeeded, failed, processingMs) {
|
|
1281
|
+
this.workerStats.tasks_processed += succeeded + failed;
|
|
1282
|
+
this.workerStats.tasks_succeeded += succeeded;
|
|
1283
|
+
this.workerStats.tasks_failed += failed;
|
|
1284
|
+
this.totalProcessingMs += processingMs;
|
|
1285
|
+
if (this.workerStats.tasks_processed > 0) {
|
|
1286
|
+
this.workerStats.avg_processing_ms = this.totalProcessingMs / this.workerStats.tasks_processed;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
emitLifecycleEvent(callback, ctx) {
|
|
1290
|
+
if (!callback) return;
|
|
1291
|
+
try {
|
|
1292
|
+
const result = callback(ctx);
|
|
1293
|
+
if (this.config.lifecycle?.mode === "sync" && result instanceof Promise) {
|
|
1294
|
+
result.catch((err) => {
|
|
1295
|
+
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
1296
|
+
});
|
|
1297
|
+
} else if (result instanceof Promise) {
|
|
1298
|
+
result.catch((err) => {
|
|
1299
|
+
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
895
1306
|
processMatureTasks(abortSignal) {
|
|
896
1307
|
if (this.matureTaskTimer) clearInterval(this.matureTaskTimer);
|
|
897
1308
|
if (abortSignal?.aborted) {
|
|
@@ -935,14 +1346,20 @@ class TaskHandler {
|
|
|
935
1346
|
});
|
|
936
1347
|
});
|
|
937
1348
|
}
|
|
938
|
-
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1349
|
+
buildTaskContext(task) {
|
|
1350
|
+
const maxRetries = this.getRetryCount(task);
|
|
1351
|
+
const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
1352
|
+
const payload = task.payload;
|
|
1353
|
+
return {
|
|
1354
|
+
task_id: task.id?.toString() || "",
|
|
1355
|
+
task_hash: payload?.task_hash,
|
|
1356
|
+
task_type: task.type,
|
|
1357
|
+
queue_id: task.queue_id,
|
|
1358
|
+
payload: this.config.lifecycle?.include_payload ? payload : {},
|
|
1359
|
+
attempt: retryCount + 1,
|
|
1360
|
+
max_retries: maxRetries,
|
|
1361
|
+
scheduled_at: task.created_at || /* @__PURE__ */ new Date()
|
|
1362
|
+
};
|
|
946
1363
|
}
|
|
947
1364
|
processBatch(queueId, processor, limit, abortSignal) {
|
|
948
1365
|
if (abortSignal?.aborted) {
|
|
@@ -1054,6 +1471,7 @@ class TaskHandler {
|
|
|
1054
1471
|
}
|
|
1055
1472
|
}
|
|
1056
1473
|
}
|
|
1474
|
+
const logger = new Logger("TaskQueuesManager", LogLevel.INFO);
|
|
1057
1475
|
class TaskQueuesManager {
|
|
1058
1476
|
constructor(messageQueue) {
|
|
1059
1477
|
this.messageQueue = messageQueue;
|
|
@@ -1073,7 +1491,7 @@ class TaskQueuesManager {
|
|
|
1073
1491
|
}
|
|
1074
1492
|
const queueMap = this.queueTaskExecutorMap.get(queueName);
|
|
1075
1493
|
queueMap.set(taskType, executor);
|
|
1076
|
-
|
|
1494
|
+
logger.info(`Registered task executor for ${taskType} on queue ${queueName}`);
|
|
1077
1495
|
}
|
|
1078
1496
|
/**
|
|
1079
1497
|
* Gets the task executor for a specific queue and task type
|
|
@@ -1110,10 +1528,67 @@ class TaskQueuesManager {
|
|
|
1110
1528
|
return Array.from(queueMap.keys());
|
|
1111
1529
|
}
|
|
1112
1530
|
}
|
|
1531
|
+
class ConsoleHealthProvider {
|
|
1532
|
+
constructor(prefix = "[Health]") {
|
|
1533
|
+
this.prefix = prefix;
|
|
1534
|
+
}
|
|
1535
|
+
// ============ Task Lifecycle ============
|
|
1536
|
+
onTaskScheduled(ctx) {
|
|
1537
|
+
console.log(`${this.prefix} Task scheduled: ${ctx.task_type} (${ctx.task_id}) in queue ${ctx.queue_id}`);
|
|
1538
|
+
}
|
|
1539
|
+
onTaskStarted(ctx) {
|
|
1540
|
+
console.log(`${this.prefix} Task started: ${ctx.task_type} (${ctx.task_id}) - queued for ${ctx.queued_duration_ms}ms`);
|
|
1541
|
+
}
|
|
1542
|
+
onTaskCompleted(ctx) {
|
|
1543
|
+
console.log(
|
|
1544
|
+
`${this.prefix} Task completed: ${ctx.task_type} (${ctx.task_id}) - processing: ${ctx.timing.processing_duration_ms}ms, total: ${ctx.timing.total_duration_ms}ms`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
onTaskFailed(ctx) {
|
|
1548
|
+
console.log(
|
|
1549
|
+
`${this.prefix} Task failed: ${ctx.task_type} (${ctx.task_id}) - error: ${ctx.error.message}, will_retry: ${ctx.will_retry}`
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
onTaskExhausted(ctx) {
|
|
1553
|
+
console.log(
|
|
1554
|
+
`${this.prefix} Task exhausted: ${ctx.task_type} (${ctx.task_id}) - total attempts: ${ctx.total_attempts}, error: ${ctx.error.message}`
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
onTaskCancelled(ctx) {
|
|
1558
|
+
console.log(`${this.prefix} Task cancelled: ${ctx.task_type} (${ctx.task_id}) - reason: ${ctx.reason}`);
|
|
1559
|
+
}
|
|
1560
|
+
// ============ Worker Lifecycle ============
|
|
1561
|
+
onWorkerStarted(info) {
|
|
1562
|
+
console.log(
|
|
1563
|
+
`${this.prefix} Worker started: ${info.worker_id} on ${info.hostname} (PID: ${info.pid}) - queues: [${info.enabled_queues.join(", ")}]`
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
onWorkerHeartbeat(info) {
|
|
1567
|
+
console.log(
|
|
1568
|
+
`${this.prefix} Worker heartbeat: ${info.worker_id} - processed: ${info.stats.tasks_processed}, success: ${info.stats.tasks_succeeded}, failed: ${info.stats.tasks_failed}, avg: ${info.stats.avg_processing_ms.toFixed(2)}ms, memory: ${info.memory_usage_mb.toFixed(2)}MB`
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
onWorkerStopped(info) {
|
|
1572
|
+
console.log(
|
|
1573
|
+
`${this.prefix} Worker stopped: ${info.worker_id} - reason: ${info.reason} - final stats: ${info.final_stats.tasks_processed} processed, ${info.final_stats.tasks_succeeded} succeeded, ${info.final_stats.tasks_failed} failed`
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
onBatchStarted(info) {
|
|
1577
|
+
console.log(
|
|
1578
|
+
`${this.prefix} Batch started: ${info.batch_size} tasks - types: [${info.task_types.join(", ")}]`
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
onBatchCompleted(info) {
|
|
1582
|
+
console.log(
|
|
1583
|
+
`${this.prefix} Batch completed: ${info.batch_size} tasks in ${info.duration_ms}ms - succeeded: ${info.succeeded}, failed: ${info.failed}`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1113
1587
|
export {
|
|
1114
1588
|
Actions,
|
|
1115
1589
|
AsyncActions,
|
|
1116
1590
|
AsyncTaskManager,
|
|
1591
|
+
ConsoleHealthProvider,
|
|
1117
1592
|
I as InMemoryAdapter,
|
|
1118
1593
|
M as MongoDbAdapter,
|
|
1119
1594
|
P as PrismaAdapter,
|