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