@supergrowthai/tq 1.0.1-canary.03bf985
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 +538 -0
- package/dist/core/base/interfaces.d.ts +1 -0
- package/dist/core/base/interfaces.js +2 -0
- package/dist/core/base/interfaces.js.map +1 -0
- package/dist/core/base/interfaces.mjs +2 -0
- package/dist/core/base/interfaces.mjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1734 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1734 -0
- package/dist/index.mjs.map +1 -0
- package/dist/src/adapters/IDatabaseAdapter.d.ts +61 -0
- package/dist/src/adapters/InMemoryAdapter.d.ts +24 -0
- package/dist/src/adapters/MongoDbAdapter.d.ts +34 -0
- package/dist/src/adapters/index.d.ts +4 -0
- package/dist/src/adapters/types.d.ts +9 -0
- package/dist/src/core/Actions.d.ts +33 -0
- package/dist/src/core/TaskHandler.d.ts +35 -0
- package/dist/src/core/TaskQueuesManager.d.ts +33 -0
- package/dist/src/core/TaskRunner.d.ts +26 -0
- package/dist/src/core/TaskStore.d.ts +63 -0
- package/dist/src/core/async/AsyncActions.d.ts +22 -0
- package/dist/src/core/async/AsyncTaskManager.d.ts +19 -0
- package/dist/src/core/async/async-task-manager.d.ts +25 -0
- package/dist/src/core/base/interfaces.d.ts +35 -0
- package/dist/src/core/environment.d.ts +6 -0
- package/dist/src/core/task-processor-types.d.ts +14 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/task-registry.d.ts +4 -0
- package/dist/src/test/task-queue.test.d.ts +6 -0
- package/dist/src/types.d.ts +1 -0
- package/dist/src/utils/task-id-gen.d.ts +4 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +2 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +88 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1734 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const mongodb = require("mongodb");
|
|
4
|
+
const mq = require("@supergrowthai/mq");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const moment = require("moment");
|
|
7
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
8
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
9
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
10
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
11
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
12
|
+
return LogLevel2;
|
|
13
|
+
})(LogLevel || {});
|
|
14
|
+
class Logger {
|
|
15
|
+
constructor(prefix, initialLogLevel = 1) {
|
|
16
|
+
this.prefix = prefix;
|
|
17
|
+
this.logLevel = initialLogLevel;
|
|
18
|
+
}
|
|
19
|
+
getLogLevel() {
|
|
20
|
+
return this.logLevel;
|
|
21
|
+
}
|
|
22
|
+
setLogLevel(level) {
|
|
23
|
+
this.logLevel = level;
|
|
24
|
+
}
|
|
25
|
+
debug(message, ...args) {
|
|
26
|
+
if (this.logLevel === 0) {
|
|
27
|
+
console.debug(`[${this.prefix}] ${message}`, ...args);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
info(message, ...args) {
|
|
31
|
+
if (this.logLevel <= 1) {
|
|
32
|
+
console.info(`[${this.prefix}] ${message}`, ...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
error(message, ...args) {
|
|
36
|
+
console.error(`[${this.prefix}] ${message}`, ...args);
|
|
37
|
+
}
|
|
38
|
+
warn(message, ...args) {
|
|
39
|
+
if (this.logLevel <= 2) {
|
|
40
|
+
console.warn(`[${this.prefix}] ${message}`, ...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
time(message) {
|
|
44
|
+
if (this.logLevel < 1) {
|
|
45
|
+
console.time(`[${this.prefix}] ${message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
timeEnd(message) {
|
|
49
|
+
if (this.logLevel < 1) {
|
|
50
|
+
console.timeEnd(`[${this.prefix}] ${message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class LockManager {
|
|
55
|
+
constructor(cacheProvider, options) {
|
|
56
|
+
this.cacheProvider = cacheProvider;
|
|
57
|
+
this.acquireLocks = /* @__PURE__ */ new Map();
|
|
58
|
+
this.logger = new Logger("MetricsCollector", LogLevel.INFO);
|
|
59
|
+
this.cacheProvider = cacheProvider;
|
|
60
|
+
this.prefix = options?.prefix || "lock:";
|
|
61
|
+
this.defaultTimeout = options?.defaultTimeout || 30 * 60;
|
|
62
|
+
if (options?.debugLogs)
|
|
63
|
+
this.logger.setLogLevel(LogLevel.INFO);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if a resource is locked
|
|
67
|
+
*/
|
|
68
|
+
async isLocked(resourceId) {
|
|
69
|
+
this.logger.debug("isLocked" + resourceId);
|
|
70
|
+
const exists = await this.cacheProvider.get(this.getKey(resourceId));
|
|
71
|
+
return !!exists;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Filter out locked resources from an array
|
|
75
|
+
*/
|
|
76
|
+
async filterLocked(resources, getResourceId) {
|
|
77
|
+
if (!resources.length) return [];
|
|
78
|
+
const lockChecks = resources.map((resource) => this.isLocked(getResourceId(resource)));
|
|
79
|
+
const lockResults = await Promise.all(lockChecks);
|
|
80
|
+
return resources.filter((_, index) => !lockResults[index]);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Acquire a lock
|
|
84
|
+
* @returns true if lock was acquired, false if already locked
|
|
85
|
+
*/
|
|
86
|
+
async acquire(resourceId, timeout = this.defaultTimeout) {
|
|
87
|
+
this.logger.debug("Acquire" + resourceId);
|
|
88
|
+
const existingLock = this.acquireLocks.get(resourceId);
|
|
89
|
+
if (existingLock) {
|
|
90
|
+
await existingLock;
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const lockPromise = this.acquireInternal(resourceId, timeout);
|
|
94
|
+
this.acquireLocks.set(resourceId, lockPromise);
|
|
95
|
+
try {
|
|
96
|
+
return await lockPromise;
|
|
97
|
+
} finally {
|
|
98
|
+
this.acquireLocks.delete(resourceId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Release a lock
|
|
103
|
+
*/
|
|
104
|
+
async release(resourceId) {
|
|
105
|
+
this.logger.debug("Release" + resourceId);
|
|
106
|
+
const key = this.getKey(resourceId);
|
|
107
|
+
await this.cacheProvider.del(key);
|
|
108
|
+
}
|
|
109
|
+
async acquireInternal(resourceId, timeout) {
|
|
110
|
+
if (await this.isLocked(resourceId)) return false;
|
|
111
|
+
const key = this.getKey(resourceId);
|
|
112
|
+
const result = await this.cacheProvider.set(key, "1", timeout);
|
|
113
|
+
return result === "OK" || result === "1" || result === 1;
|
|
114
|
+
}
|
|
115
|
+
getKey(resourceId) {
|
|
116
|
+
return `${this.prefix}${resourceId}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const logger$3 = new Logger("MongoDbAdapter", LogLevel.INFO);
|
|
120
|
+
const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
121
|
+
class MongoDbAdapter {
|
|
122
|
+
constructor(uri = process.env.MONGODB_URI || "mongodb://localhost:27017", dbName = process.env.DB_NAME || "taskqueue", collectionName = "scheduled") {
|
|
123
|
+
this.client = null;
|
|
124
|
+
this.db = null;
|
|
125
|
+
this.tasksCollection = null;
|
|
126
|
+
this.uri = uri;
|
|
127
|
+
this.dbName = dbName;
|
|
128
|
+
this.collectionName = collectionName;
|
|
129
|
+
}
|
|
130
|
+
async initialize() {
|
|
131
|
+
if (this.db) return;
|
|
132
|
+
this.client = new mongodb.MongoClient(this.uri);
|
|
133
|
+
await this.client.connect();
|
|
134
|
+
this.db = this.client.db(this.dbName);
|
|
135
|
+
this.tasksCollection = this.db.collection(this.collectionName);
|
|
136
|
+
await this.tasksCollection.createIndex({ execute_at: 1, status: 1 });
|
|
137
|
+
await this.tasksCollection.createIndex({ status: 1, processing_started_at: 1 });
|
|
138
|
+
await this.tasksCollection.createIndex({ expires_at: 1 }, { sparse: true });
|
|
139
|
+
logger$3.info(`Connected to MongoDB at ${this.uri}/${this.dbName}`);
|
|
140
|
+
}
|
|
141
|
+
async addTasksToScheduled(tasks) {
|
|
142
|
+
if (!tasks.length) return [];
|
|
143
|
+
const collection = await this.ensureConnection();
|
|
144
|
+
const transformedTasks = tasks.map((task) => ({
|
|
145
|
+
_id: task._id,
|
|
146
|
+
type: task.type,
|
|
147
|
+
payload: task.payload,
|
|
148
|
+
execute_at: task.execute_at,
|
|
149
|
+
status: task.status || "scheduled",
|
|
150
|
+
retries: task.retries || 0,
|
|
151
|
+
created_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
152
|
+
updated_at: /* @__PURE__ */ new Date(),
|
|
153
|
+
queue_id: task.queue_id,
|
|
154
|
+
processing_started_at: task.processing_started_at || /* @__PURE__ */ new Date(),
|
|
155
|
+
expires_at: task.expires_at,
|
|
156
|
+
task_group: task.task_group,
|
|
157
|
+
task_hash: task.task_hash,
|
|
158
|
+
retry_after: task.retry_after,
|
|
159
|
+
execution_stats: task.execution_stats,
|
|
160
|
+
force_store: task.force_store
|
|
161
|
+
}));
|
|
162
|
+
try {
|
|
163
|
+
await collection.insertMany(transformedTasks, { ordered: false });
|
|
164
|
+
return transformedTasks;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error.writeErrors) {
|
|
167
|
+
const successfulTasks = transformedTasks.filter(
|
|
168
|
+
(_, index) => !error.writeErrors.some((e) => e.index === index)
|
|
169
|
+
);
|
|
170
|
+
return successfulTasks;
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async getMatureTasks(timestamp) {
|
|
176
|
+
const collection = await this.ensureConnection();
|
|
177
|
+
const staleTimestamp = Date.now() - TWO_DAYS_MS;
|
|
178
|
+
await collection.updateMany(
|
|
179
|
+
{
|
|
180
|
+
status: "processing",
|
|
181
|
+
processing_started_at: { $lt: new Date(staleTimestamp) }
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
$set: { status: "scheduled" },
|
|
185
|
+
$inc: { retries: 1 }
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
const filter = {
|
|
189
|
+
status: "scheduled",
|
|
190
|
+
execute_at: { $lte: new Date(timestamp) }
|
|
191
|
+
};
|
|
192
|
+
const tasks = await collection.find(filter).limit(1e3).toArray();
|
|
193
|
+
if (tasks.length > 0) {
|
|
194
|
+
const taskIds = tasks.map((t) => t._id);
|
|
195
|
+
await collection.updateMany(
|
|
196
|
+
{ _id: { $in: taskIds } },
|
|
197
|
+
{
|
|
198
|
+
$set: {
|
|
199
|
+
status: "processing",
|
|
200
|
+
processing_started_at: /* @__PURE__ */ new Date()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return tasks;
|
|
206
|
+
}
|
|
207
|
+
async markTasksAsProcessing(taskIds, processingStartedAt) {
|
|
208
|
+
const collection = await this.ensureConnection();
|
|
209
|
+
await collection.updateMany(
|
|
210
|
+
{ _id: { $in: taskIds } },
|
|
211
|
+
{
|
|
212
|
+
$set: {
|
|
213
|
+
status: "processing",
|
|
214
|
+
processing_started_at: processingStartedAt,
|
|
215
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
async markTasksAsExecuted(taskIds) {
|
|
221
|
+
const collection = await this.ensureConnection();
|
|
222
|
+
await collection.updateMany(
|
|
223
|
+
{ _id: { $in: taskIds } },
|
|
224
|
+
{
|
|
225
|
+
$set: {
|
|
226
|
+
status: "executed",
|
|
227
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
async markTasksAsFailed(taskIds) {
|
|
233
|
+
const collection = await this.ensureConnection();
|
|
234
|
+
await collection.updateMany(
|
|
235
|
+
{ _id: { $in: taskIds } },
|
|
236
|
+
{
|
|
237
|
+
$set: {
|
|
238
|
+
status: "failed",
|
|
239
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
240
|
+
},
|
|
241
|
+
$inc: { retries: 1 }
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
async getTasksByIds(taskIds) {
|
|
246
|
+
const collection = await this.ensureConnection();
|
|
247
|
+
return collection.find({ _id: { $in: taskIds } }).toArray();
|
|
248
|
+
}
|
|
249
|
+
async updateTasks(updates) {
|
|
250
|
+
const collection = await this.ensureConnection();
|
|
251
|
+
const bulkOps = updates.map(({ id, updates: updates2 }) => ({
|
|
252
|
+
updateOne: {
|
|
253
|
+
filter: { _id: id },
|
|
254
|
+
update: {
|
|
255
|
+
$set: {
|
|
256
|
+
...updates2,
|
|
257
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}));
|
|
262
|
+
if (bulkOps.length > 0) {
|
|
263
|
+
await collection.bulkWrite(bulkOps);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async getCleanupStats() {
|
|
267
|
+
const collection = await this.ensureConnection();
|
|
268
|
+
const orphanedBefore = new Date(Date.now() - TWO_DAYS_MS);
|
|
269
|
+
const orphanedTasks = await collection.countDocuments({
|
|
270
|
+
status: "processing",
|
|
271
|
+
processing_started_at: { $lt: orphanedBefore }
|
|
272
|
+
});
|
|
273
|
+
const expiredTasks = await collection.countDocuments({
|
|
274
|
+
expires_at: { $lt: /* @__PURE__ */ new Date() }
|
|
275
|
+
});
|
|
276
|
+
return { orphanedTasks, expiredTasks };
|
|
277
|
+
}
|
|
278
|
+
async cleanupTasks(orphanedBefore, expiredBefore) {
|
|
279
|
+
const collection = await this.ensureConnection();
|
|
280
|
+
await collection.deleteMany({
|
|
281
|
+
status: "processing",
|
|
282
|
+
processing_started_at: { $lt: orphanedBefore }
|
|
283
|
+
});
|
|
284
|
+
await collection.deleteMany({
|
|
285
|
+
expires_at: { $lt: expiredBefore }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async close() {
|
|
289
|
+
if (this.client) {
|
|
290
|
+
await this.client.close();
|
|
291
|
+
this.client = null;
|
|
292
|
+
this.db = null;
|
|
293
|
+
this.tasksCollection = null;
|
|
294
|
+
logger$3.info("Disconnected from MongoDB");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async ensureConnection() {
|
|
298
|
+
if (!this.tasksCollection) {
|
|
299
|
+
await this.initialize();
|
|
300
|
+
}
|
|
301
|
+
return this.tasksCollection;
|
|
302
|
+
}
|
|
303
|
+
generateId() {
|
|
304
|
+
return new mongodb.ObjectId();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
class InMemoryAdapter {
|
|
308
|
+
constructor() {
|
|
309
|
+
this.scheduledTasks = /* @__PURE__ */ new Map();
|
|
310
|
+
}
|
|
311
|
+
async addTasksToScheduled(tasks) {
|
|
312
|
+
const addedTasks = tasks.map((task) => {
|
|
313
|
+
const id = task._id;
|
|
314
|
+
const taskWithId = { ...task };
|
|
315
|
+
this.scheduledTasks.set(id, taskWithId);
|
|
316
|
+
return taskWithId;
|
|
317
|
+
});
|
|
318
|
+
return addedTasks;
|
|
319
|
+
}
|
|
320
|
+
async getMatureTasks(timestamp) {
|
|
321
|
+
const matureTasks = [];
|
|
322
|
+
for (const [id, task] of Array.from(this.scheduledTasks.entries())) {
|
|
323
|
+
if (task.execute_at.getTime() <= timestamp && task.status !== "processing" && task.status !== "executed") {
|
|
324
|
+
matureTasks.push(task);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return matureTasks;
|
|
328
|
+
}
|
|
329
|
+
async markTasksAsProcessing(taskIds, processingStartedAt) {
|
|
330
|
+
for (const id of taskIds) {
|
|
331
|
+
const task = this.scheduledTasks.get(id);
|
|
332
|
+
if (task) {
|
|
333
|
+
task.status = "processing";
|
|
334
|
+
task.processing_started_at = processingStartedAt;
|
|
335
|
+
this.scheduledTasks.set(id, task);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async markTasksAsExecuted(taskIds) {
|
|
340
|
+
for (const id of taskIds) {
|
|
341
|
+
const task = this.scheduledTasks.get(id);
|
|
342
|
+
if (task) {
|
|
343
|
+
task.status = "executed";
|
|
344
|
+
task.execute_at = /* @__PURE__ */ new Date();
|
|
345
|
+
this.scheduledTasks.set(id, task);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async markTasksAsFailed(taskIds) {
|
|
350
|
+
for (const id of taskIds) {
|
|
351
|
+
const task = this.scheduledTasks.get(id);
|
|
352
|
+
if (task) {
|
|
353
|
+
task.status = "failed";
|
|
354
|
+
task.execution_stats = { ...task.execution_stats, failed_at: /* @__PURE__ */ new Date() };
|
|
355
|
+
this.scheduledTasks.set(id, task);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async getTasksByIds(taskIds) {
|
|
360
|
+
return taskIds.map((id) => this.scheduledTasks.get(id)).filter(Boolean);
|
|
361
|
+
}
|
|
362
|
+
async updateTasks(updates) {
|
|
363
|
+
for (const { id, updates: taskUpdates } of updates) {
|
|
364
|
+
const task = this.scheduledTasks.get(id);
|
|
365
|
+
if (task) {
|
|
366
|
+
Object.assign(task, taskUpdates);
|
|
367
|
+
this.scheduledTasks.set(id, task);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
async getCleanupStats() {
|
|
372
|
+
let orphanedTasks = 0;
|
|
373
|
+
let expiredTasks = 0;
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
for (const task of Array.from(this.scheduledTasks.values())) {
|
|
376
|
+
if (task.status === "processing" && task.processing_started_at && now - task.processing_started_at.getTime() > 3e5) {
|
|
377
|
+
orphanedTasks++;
|
|
378
|
+
}
|
|
379
|
+
if (task.expires_at && now > task.expires_at.getTime()) {
|
|
380
|
+
expiredTasks++;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return { orphanedTasks, expiredTasks };
|
|
384
|
+
}
|
|
385
|
+
async cleanupTasks(orphanedBefore, expiredBefore) {
|
|
386
|
+
for (const [id, task] of Array.from(this.scheduledTasks.entries())) {
|
|
387
|
+
const shouldDelete = task.status === "processing" && task.processing_started_at && task.processing_started_at < orphanedBefore || task.expires_at && task.expires_at < expiredBefore;
|
|
388
|
+
if (shouldDelete) {
|
|
389
|
+
this.scheduledTasks.delete(id);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async initialize() {
|
|
394
|
+
}
|
|
395
|
+
async close() {
|
|
396
|
+
this.scheduledTasks.clear();
|
|
397
|
+
}
|
|
398
|
+
generateId() {
|
|
399
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
class TaskStore {
|
|
403
|
+
constructor(databaseAdapter) {
|
|
404
|
+
this.databaseAdapter = databaseAdapter;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Adds multiple tasks to the scheduled queue
|
|
408
|
+
*/
|
|
409
|
+
async addTasksToScheduled(tasks) {
|
|
410
|
+
if (!tasks.length) return [];
|
|
411
|
+
const transformedTasks = tasks.map((task) => ({
|
|
412
|
+
_id: task._id,
|
|
413
|
+
type: task.type,
|
|
414
|
+
queue_id: mq.getEnvironmentQueueName(task.queue_id),
|
|
415
|
+
payload: task.payload,
|
|
416
|
+
execute_at: task.execute_at,
|
|
417
|
+
expires_at: task.expires_at,
|
|
418
|
+
status: "scheduled",
|
|
419
|
+
task_group: task.task_group,
|
|
420
|
+
task_hash: task.task_hash,
|
|
421
|
+
retries: task.retries || 0,
|
|
422
|
+
retry_after: task.retry_after,
|
|
423
|
+
execution_stats: task.execution_stats,
|
|
424
|
+
created_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
425
|
+
updated_at: task.updated_at || /* @__PURE__ */ new Date(),
|
|
426
|
+
processing_started_at: task.processing_started_at || /* @__PURE__ */ new Date(),
|
|
427
|
+
force_store: task.force_store
|
|
428
|
+
}));
|
|
429
|
+
return await this.databaseAdapter.addTasksToScheduled(transformedTasks);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Gets mature tasks that are ready to be processed
|
|
433
|
+
* Implements a two-phase approach:
|
|
434
|
+
* 1. Reset stale processing tasks
|
|
435
|
+
* 2. Fetch and mark new mature tasks
|
|
436
|
+
*/
|
|
437
|
+
async getMatureTasks(timestamp) {
|
|
438
|
+
return await this.databaseAdapter.getMatureTasks(timestamp);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Marks tasks as processing with current timestamp
|
|
442
|
+
*/
|
|
443
|
+
async markTasksAsProcessing(taskIds) {
|
|
444
|
+
await this.databaseAdapter.markTasksAsProcessing(taskIds, /* @__PURE__ */ new Date());
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Marks tasks as executed/completed
|
|
448
|
+
*/
|
|
449
|
+
async markTasksAsExecuted(taskIds) {
|
|
450
|
+
await this.databaseAdapter.markTasksAsExecuted(taskIds);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Marks tasks as failed and increments retry count
|
|
454
|
+
*/
|
|
455
|
+
async markTasksAsFailed(taskIds) {
|
|
456
|
+
await this.databaseAdapter.markTasksAsFailed(taskIds);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Updates multiple tasks with specific updates
|
|
460
|
+
*/
|
|
461
|
+
async updateTasks(updates) {
|
|
462
|
+
await this.databaseAdapter.updateTasks(updates);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Gets tasks by their IDs
|
|
466
|
+
*/
|
|
467
|
+
async getTasksByIds(taskIds) {
|
|
468
|
+
return await this.databaseAdapter.getTasksByIds(taskIds);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Gets cleanup statistics
|
|
472
|
+
*/
|
|
473
|
+
async getCleanupStats() {
|
|
474
|
+
return await this.databaseAdapter.getCleanupStats();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Cleans up orphaned and expired tasks
|
|
478
|
+
*/
|
|
479
|
+
async cleanupTasks() {
|
|
480
|
+
const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1e3);
|
|
481
|
+
const now = /* @__PURE__ */ new Date();
|
|
482
|
+
await this.databaseAdapter.cleanupTasks(twoDaysAgo, now);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Updates tasks for retry with new execution time and retry count
|
|
486
|
+
*/
|
|
487
|
+
async updateTasksForRetry(tasks) {
|
|
488
|
+
const updates = tasks.map((task) => ({
|
|
489
|
+
id: task._id,
|
|
490
|
+
updates: {
|
|
491
|
+
execute_at: task.execute_at,
|
|
492
|
+
status: task.status,
|
|
493
|
+
execution_stats: task.execution_stats,
|
|
494
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
495
|
+
}
|
|
496
|
+
}));
|
|
497
|
+
await this.databaseAdapter.updateTasks(updates);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Marks tasks as successful/completed
|
|
501
|
+
*/
|
|
502
|
+
async markTasksAsSuccess(tasks) {
|
|
503
|
+
const taskIds = tasks.map((task) => task._id);
|
|
504
|
+
await this.databaseAdapter.markTasksAsExecuted(taskIds);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Marks tasks as ignored (same as failed for now)
|
|
508
|
+
*/
|
|
509
|
+
async markTasksAsIgnored(tasks) {
|
|
510
|
+
const taskIds = tasks.map((task) => task._id);
|
|
511
|
+
await this.databaseAdapter.markTasksAsFailed(taskIds);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
515
|
+
function getDefaultExportFromCjs(x) {
|
|
516
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
517
|
+
}
|
|
518
|
+
var _baseSlice;
|
|
519
|
+
var hasRequired_baseSlice;
|
|
520
|
+
function require_baseSlice() {
|
|
521
|
+
if (hasRequired_baseSlice) return _baseSlice;
|
|
522
|
+
hasRequired_baseSlice = 1;
|
|
523
|
+
function baseSlice(array, start, end) {
|
|
524
|
+
var index = -1, length = array.length;
|
|
525
|
+
if (start < 0) {
|
|
526
|
+
start = -start > length ? 0 : length + start;
|
|
527
|
+
}
|
|
528
|
+
end = end > length ? length : end;
|
|
529
|
+
if (end < 0) {
|
|
530
|
+
end += length;
|
|
531
|
+
}
|
|
532
|
+
length = start > end ? 0 : end - start >>> 0;
|
|
533
|
+
start >>>= 0;
|
|
534
|
+
var result = Array(length);
|
|
535
|
+
while (++index < length) {
|
|
536
|
+
result[index] = array[index + start];
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
_baseSlice = baseSlice;
|
|
541
|
+
return _baseSlice;
|
|
542
|
+
}
|
|
543
|
+
var eq_1;
|
|
544
|
+
var hasRequiredEq;
|
|
545
|
+
function requireEq() {
|
|
546
|
+
if (hasRequiredEq) return eq_1;
|
|
547
|
+
hasRequiredEq = 1;
|
|
548
|
+
function eq(value, other) {
|
|
549
|
+
return value === other || value !== value && other !== other;
|
|
550
|
+
}
|
|
551
|
+
eq_1 = eq;
|
|
552
|
+
return eq_1;
|
|
553
|
+
}
|
|
554
|
+
var _freeGlobal;
|
|
555
|
+
var hasRequired_freeGlobal;
|
|
556
|
+
function require_freeGlobal() {
|
|
557
|
+
if (hasRequired_freeGlobal) return _freeGlobal;
|
|
558
|
+
hasRequired_freeGlobal = 1;
|
|
559
|
+
var freeGlobal = typeof commonjsGlobal == "object" && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
|
|
560
|
+
_freeGlobal = freeGlobal;
|
|
561
|
+
return _freeGlobal;
|
|
562
|
+
}
|
|
563
|
+
var _root;
|
|
564
|
+
var hasRequired_root;
|
|
565
|
+
function require_root() {
|
|
566
|
+
if (hasRequired_root) return _root;
|
|
567
|
+
hasRequired_root = 1;
|
|
568
|
+
var freeGlobal = require_freeGlobal();
|
|
569
|
+
var freeSelf = typeof self == "object" && self && self.Object === Object && self;
|
|
570
|
+
var root = freeGlobal || freeSelf || Function("return this")();
|
|
571
|
+
_root = root;
|
|
572
|
+
return _root;
|
|
573
|
+
}
|
|
574
|
+
var _Symbol;
|
|
575
|
+
var hasRequired_Symbol;
|
|
576
|
+
function require_Symbol() {
|
|
577
|
+
if (hasRequired_Symbol) return _Symbol;
|
|
578
|
+
hasRequired_Symbol = 1;
|
|
579
|
+
var root = require_root();
|
|
580
|
+
var Symbol2 = root.Symbol;
|
|
581
|
+
_Symbol = Symbol2;
|
|
582
|
+
return _Symbol;
|
|
583
|
+
}
|
|
584
|
+
var _getRawTag;
|
|
585
|
+
var hasRequired_getRawTag;
|
|
586
|
+
function require_getRawTag() {
|
|
587
|
+
if (hasRequired_getRawTag) return _getRawTag;
|
|
588
|
+
hasRequired_getRawTag = 1;
|
|
589
|
+
var Symbol2 = require_Symbol();
|
|
590
|
+
var objectProto = Object.prototype;
|
|
591
|
+
var hasOwnProperty = objectProto.hasOwnProperty;
|
|
592
|
+
var nativeObjectToString = objectProto.toString;
|
|
593
|
+
var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
|
|
594
|
+
function getRawTag(value) {
|
|
595
|
+
var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
|
|
596
|
+
try {
|
|
597
|
+
value[symToStringTag] = void 0;
|
|
598
|
+
var unmasked = true;
|
|
599
|
+
} catch (e) {
|
|
600
|
+
}
|
|
601
|
+
var result = nativeObjectToString.call(value);
|
|
602
|
+
if (unmasked) {
|
|
603
|
+
if (isOwn) {
|
|
604
|
+
value[symToStringTag] = tag;
|
|
605
|
+
} else {
|
|
606
|
+
delete value[symToStringTag];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
_getRawTag = getRawTag;
|
|
612
|
+
return _getRawTag;
|
|
613
|
+
}
|
|
614
|
+
var _objectToString;
|
|
615
|
+
var hasRequired_objectToString;
|
|
616
|
+
function require_objectToString() {
|
|
617
|
+
if (hasRequired_objectToString) return _objectToString;
|
|
618
|
+
hasRequired_objectToString = 1;
|
|
619
|
+
var objectProto = Object.prototype;
|
|
620
|
+
var nativeObjectToString = objectProto.toString;
|
|
621
|
+
function objectToString(value) {
|
|
622
|
+
return nativeObjectToString.call(value);
|
|
623
|
+
}
|
|
624
|
+
_objectToString = objectToString;
|
|
625
|
+
return _objectToString;
|
|
626
|
+
}
|
|
627
|
+
var _baseGetTag;
|
|
628
|
+
var hasRequired_baseGetTag;
|
|
629
|
+
function require_baseGetTag() {
|
|
630
|
+
if (hasRequired_baseGetTag) return _baseGetTag;
|
|
631
|
+
hasRequired_baseGetTag = 1;
|
|
632
|
+
var Symbol2 = require_Symbol(), getRawTag = require_getRawTag(), objectToString = require_objectToString();
|
|
633
|
+
var nullTag = "[object Null]", undefinedTag = "[object Undefined]";
|
|
634
|
+
var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
|
|
635
|
+
function baseGetTag(value) {
|
|
636
|
+
if (value == null) {
|
|
637
|
+
return value === void 0 ? undefinedTag : nullTag;
|
|
638
|
+
}
|
|
639
|
+
return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
|
|
640
|
+
}
|
|
641
|
+
_baseGetTag = baseGetTag;
|
|
642
|
+
return _baseGetTag;
|
|
643
|
+
}
|
|
644
|
+
var isObject_1;
|
|
645
|
+
var hasRequiredIsObject;
|
|
646
|
+
function requireIsObject() {
|
|
647
|
+
if (hasRequiredIsObject) return isObject_1;
|
|
648
|
+
hasRequiredIsObject = 1;
|
|
649
|
+
function isObject(value) {
|
|
650
|
+
var type = typeof value;
|
|
651
|
+
return value != null && (type == "object" || type == "function");
|
|
652
|
+
}
|
|
653
|
+
isObject_1 = isObject;
|
|
654
|
+
return isObject_1;
|
|
655
|
+
}
|
|
656
|
+
var isFunction_1;
|
|
657
|
+
var hasRequiredIsFunction;
|
|
658
|
+
function requireIsFunction() {
|
|
659
|
+
if (hasRequiredIsFunction) return isFunction_1;
|
|
660
|
+
hasRequiredIsFunction = 1;
|
|
661
|
+
var baseGetTag = require_baseGetTag(), isObject = requireIsObject();
|
|
662
|
+
var asyncTag = "[object AsyncFunction]", funcTag = "[object Function]", genTag = "[object GeneratorFunction]", proxyTag = "[object Proxy]";
|
|
663
|
+
function isFunction(value) {
|
|
664
|
+
if (!isObject(value)) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
var tag = baseGetTag(value);
|
|
668
|
+
return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
|
|
669
|
+
}
|
|
670
|
+
isFunction_1 = isFunction;
|
|
671
|
+
return isFunction_1;
|
|
672
|
+
}
|
|
673
|
+
var isLength_1;
|
|
674
|
+
var hasRequiredIsLength;
|
|
675
|
+
function requireIsLength() {
|
|
676
|
+
if (hasRequiredIsLength) return isLength_1;
|
|
677
|
+
hasRequiredIsLength = 1;
|
|
678
|
+
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
679
|
+
function isLength(value) {
|
|
680
|
+
return typeof value == "number" && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
|
|
681
|
+
}
|
|
682
|
+
isLength_1 = isLength;
|
|
683
|
+
return isLength_1;
|
|
684
|
+
}
|
|
685
|
+
var isArrayLike_1;
|
|
686
|
+
var hasRequiredIsArrayLike;
|
|
687
|
+
function requireIsArrayLike() {
|
|
688
|
+
if (hasRequiredIsArrayLike) return isArrayLike_1;
|
|
689
|
+
hasRequiredIsArrayLike = 1;
|
|
690
|
+
var isFunction = requireIsFunction(), isLength = requireIsLength();
|
|
691
|
+
function isArrayLike(value) {
|
|
692
|
+
return value != null && isLength(value.length) && !isFunction(value);
|
|
693
|
+
}
|
|
694
|
+
isArrayLike_1 = isArrayLike;
|
|
695
|
+
return isArrayLike_1;
|
|
696
|
+
}
|
|
697
|
+
var _isIndex;
|
|
698
|
+
var hasRequired_isIndex;
|
|
699
|
+
function require_isIndex() {
|
|
700
|
+
if (hasRequired_isIndex) return _isIndex;
|
|
701
|
+
hasRequired_isIndex = 1;
|
|
702
|
+
var MAX_SAFE_INTEGER = 9007199254740991;
|
|
703
|
+
var reIsUint = /^(?:0|[1-9]\d*)$/;
|
|
704
|
+
function isIndex(value, length) {
|
|
705
|
+
var type = typeof value;
|
|
706
|
+
length = length == null ? MAX_SAFE_INTEGER : length;
|
|
707
|
+
return !!length && (type == "number" || type != "symbol" && reIsUint.test(value)) && (value > -1 && value % 1 == 0 && value < length);
|
|
708
|
+
}
|
|
709
|
+
_isIndex = isIndex;
|
|
710
|
+
return _isIndex;
|
|
711
|
+
}
|
|
712
|
+
var _isIterateeCall;
|
|
713
|
+
var hasRequired_isIterateeCall;
|
|
714
|
+
function require_isIterateeCall() {
|
|
715
|
+
if (hasRequired_isIterateeCall) return _isIterateeCall;
|
|
716
|
+
hasRequired_isIterateeCall = 1;
|
|
717
|
+
var eq = requireEq(), isArrayLike = requireIsArrayLike(), isIndex = require_isIndex(), isObject = requireIsObject();
|
|
718
|
+
function isIterateeCall(value, index, object) {
|
|
719
|
+
if (!isObject(object)) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
var type = typeof index;
|
|
723
|
+
if (type == "number" ? isArrayLike(object) && isIndex(index, object.length) : type == "string" && index in object) {
|
|
724
|
+
return eq(object[index], value);
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
_isIterateeCall = isIterateeCall;
|
|
729
|
+
return _isIterateeCall;
|
|
730
|
+
}
|
|
731
|
+
var _trimmedEndIndex;
|
|
732
|
+
var hasRequired_trimmedEndIndex;
|
|
733
|
+
function require_trimmedEndIndex() {
|
|
734
|
+
if (hasRequired_trimmedEndIndex) return _trimmedEndIndex;
|
|
735
|
+
hasRequired_trimmedEndIndex = 1;
|
|
736
|
+
var reWhitespace = /\s/;
|
|
737
|
+
function trimmedEndIndex(string) {
|
|
738
|
+
var index = string.length;
|
|
739
|
+
while (index-- && reWhitespace.test(string.charAt(index))) {
|
|
740
|
+
}
|
|
741
|
+
return index;
|
|
742
|
+
}
|
|
743
|
+
_trimmedEndIndex = trimmedEndIndex;
|
|
744
|
+
return _trimmedEndIndex;
|
|
745
|
+
}
|
|
746
|
+
var _baseTrim;
|
|
747
|
+
var hasRequired_baseTrim;
|
|
748
|
+
function require_baseTrim() {
|
|
749
|
+
if (hasRequired_baseTrim) return _baseTrim;
|
|
750
|
+
hasRequired_baseTrim = 1;
|
|
751
|
+
var trimmedEndIndex = require_trimmedEndIndex();
|
|
752
|
+
var reTrimStart = /^\s+/;
|
|
753
|
+
function baseTrim(string) {
|
|
754
|
+
return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, "") : string;
|
|
755
|
+
}
|
|
756
|
+
_baseTrim = baseTrim;
|
|
757
|
+
return _baseTrim;
|
|
758
|
+
}
|
|
759
|
+
var isObjectLike_1;
|
|
760
|
+
var hasRequiredIsObjectLike;
|
|
761
|
+
function requireIsObjectLike() {
|
|
762
|
+
if (hasRequiredIsObjectLike) return isObjectLike_1;
|
|
763
|
+
hasRequiredIsObjectLike = 1;
|
|
764
|
+
function isObjectLike(value) {
|
|
765
|
+
return value != null && typeof value == "object";
|
|
766
|
+
}
|
|
767
|
+
isObjectLike_1 = isObjectLike;
|
|
768
|
+
return isObjectLike_1;
|
|
769
|
+
}
|
|
770
|
+
var isSymbol_1;
|
|
771
|
+
var hasRequiredIsSymbol;
|
|
772
|
+
function requireIsSymbol() {
|
|
773
|
+
if (hasRequiredIsSymbol) return isSymbol_1;
|
|
774
|
+
hasRequiredIsSymbol = 1;
|
|
775
|
+
var baseGetTag = require_baseGetTag(), isObjectLike = requireIsObjectLike();
|
|
776
|
+
var symbolTag = "[object Symbol]";
|
|
777
|
+
function isSymbol(value) {
|
|
778
|
+
return typeof value == "symbol" || isObjectLike(value) && baseGetTag(value) == symbolTag;
|
|
779
|
+
}
|
|
780
|
+
isSymbol_1 = isSymbol;
|
|
781
|
+
return isSymbol_1;
|
|
782
|
+
}
|
|
783
|
+
var toNumber_1;
|
|
784
|
+
var hasRequiredToNumber;
|
|
785
|
+
function requireToNumber() {
|
|
786
|
+
if (hasRequiredToNumber) return toNumber_1;
|
|
787
|
+
hasRequiredToNumber = 1;
|
|
788
|
+
var baseTrim = require_baseTrim(), isObject = requireIsObject(), isSymbol = requireIsSymbol();
|
|
789
|
+
var NAN = 0 / 0;
|
|
790
|
+
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
|
|
791
|
+
var reIsBinary = /^0b[01]+$/i;
|
|
792
|
+
var reIsOctal = /^0o[0-7]+$/i;
|
|
793
|
+
var freeParseInt = parseInt;
|
|
794
|
+
function toNumber(value) {
|
|
795
|
+
if (typeof value == "number") {
|
|
796
|
+
return value;
|
|
797
|
+
}
|
|
798
|
+
if (isSymbol(value)) {
|
|
799
|
+
return NAN;
|
|
800
|
+
}
|
|
801
|
+
if (isObject(value)) {
|
|
802
|
+
var other = typeof value.valueOf == "function" ? value.valueOf() : value;
|
|
803
|
+
value = isObject(other) ? other + "" : other;
|
|
804
|
+
}
|
|
805
|
+
if (typeof value != "string") {
|
|
806
|
+
return value === 0 ? value : +value;
|
|
807
|
+
}
|
|
808
|
+
value = baseTrim(value);
|
|
809
|
+
var isBinary = reIsBinary.test(value);
|
|
810
|
+
return isBinary || reIsOctal.test(value) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : reIsBadHex.test(value) ? NAN : +value;
|
|
811
|
+
}
|
|
812
|
+
toNumber_1 = toNumber;
|
|
813
|
+
return toNumber_1;
|
|
814
|
+
}
|
|
815
|
+
var toFinite_1;
|
|
816
|
+
var hasRequiredToFinite;
|
|
817
|
+
function requireToFinite() {
|
|
818
|
+
if (hasRequiredToFinite) return toFinite_1;
|
|
819
|
+
hasRequiredToFinite = 1;
|
|
820
|
+
var toNumber = requireToNumber();
|
|
821
|
+
var INFINITY = 1 / 0, MAX_INTEGER = 17976931348623157e292;
|
|
822
|
+
function toFinite(value) {
|
|
823
|
+
if (!value) {
|
|
824
|
+
return value === 0 ? value : 0;
|
|
825
|
+
}
|
|
826
|
+
value = toNumber(value);
|
|
827
|
+
if (value === INFINITY || value === -INFINITY) {
|
|
828
|
+
var sign = value < 0 ? -1 : 1;
|
|
829
|
+
return sign * MAX_INTEGER;
|
|
830
|
+
}
|
|
831
|
+
return value === value ? value : 0;
|
|
832
|
+
}
|
|
833
|
+
toFinite_1 = toFinite;
|
|
834
|
+
return toFinite_1;
|
|
835
|
+
}
|
|
836
|
+
var toInteger_1;
|
|
837
|
+
var hasRequiredToInteger;
|
|
838
|
+
function requireToInteger() {
|
|
839
|
+
if (hasRequiredToInteger) return toInteger_1;
|
|
840
|
+
hasRequiredToInteger = 1;
|
|
841
|
+
var toFinite = requireToFinite();
|
|
842
|
+
function toInteger(value) {
|
|
843
|
+
var result = toFinite(value), remainder = result % 1;
|
|
844
|
+
return result === result ? remainder ? result - remainder : result : 0;
|
|
845
|
+
}
|
|
846
|
+
toInteger_1 = toInteger;
|
|
847
|
+
return toInteger_1;
|
|
848
|
+
}
|
|
849
|
+
var chunk_1;
|
|
850
|
+
var hasRequiredChunk;
|
|
851
|
+
function requireChunk() {
|
|
852
|
+
if (hasRequiredChunk) return chunk_1;
|
|
853
|
+
hasRequiredChunk = 1;
|
|
854
|
+
var baseSlice = require_baseSlice(), isIterateeCall = require_isIterateeCall(), toInteger = requireToInteger();
|
|
855
|
+
var nativeCeil = Math.ceil, nativeMax = Math.max;
|
|
856
|
+
function chunk2(array, size, guard) {
|
|
857
|
+
if (guard ? isIterateeCall(array, size, guard) : size === void 0) {
|
|
858
|
+
size = 1;
|
|
859
|
+
} else {
|
|
860
|
+
size = nativeMax(toInteger(size), 0);
|
|
861
|
+
}
|
|
862
|
+
var length = array == null ? 0 : array.length;
|
|
863
|
+
if (!length || size < 1) {
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
var index = 0, resIndex = 0, result = Array(nativeCeil(length / size));
|
|
867
|
+
while (index < length) {
|
|
868
|
+
result[resIndex++] = baseSlice(array, index, index += size);
|
|
869
|
+
}
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
chunk_1 = chunk2;
|
|
873
|
+
return chunk_1;
|
|
874
|
+
}
|
|
875
|
+
var chunkExports = requireChunk();
|
|
876
|
+
const chunk = /* @__PURE__ */ getDefaultExportFromCjs(chunkExports);
|
|
877
|
+
let taskIdCacheKeyGen = null;
|
|
878
|
+
function tId(task) {
|
|
879
|
+
if (task._id) return task._id.toString();
|
|
880
|
+
if (task.task_hash) return task.task_hash;
|
|
881
|
+
if (!taskIdCacheKeyGen) {
|
|
882
|
+
const hash = crypto.createHash("sha256");
|
|
883
|
+
hash.update(JSON.stringify({
|
|
884
|
+
type: task.type,
|
|
885
|
+
queue_id: task.queue_id,
|
|
886
|
+
created_at: task.created_at,
|
|
887
|
+
payload: task.payload
|
|
888
|
+
}));
|
|
889
|
+
return `task-id-gen:${hash.digest("hex")}`;
|
|
890
|
+
}
|
|
891
|
+
return taskIdCacheKeyGen.for(task.type, task.queue_id, task.created_at, task.payload);
|
|
892
|
+
}
|
|
893
|
+
function setKeyGenerator(generator) {
|
|
894
|
+
taskIdCacheKeyGen = generator;
|
|
895
|
+
}
|
|
896
|
+
const logger$2 = new Logger("Actions", LogLevel.INFO);
|
|
897
|
+
class Actions {
|
|
898
|
+
constructor(taskRunnerId) {
|
|
899
|
+
this.taskContexts = /* @__PURE__ */ new Map();
|
|
900
|
+
this.taskRunnerId = taskRunnerId;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Fork execution context for a specific task (for single-task executors)
|
|
904
|
+
*/
|
|
905
|
+
forkForTask(task) {
|
|
906
|
+
const taskId = tId(task);
|
|
907
|
+
const context = { task, actions: [] };
|
|
908
|
+
this.taskContexts.set(taskId, context);
|
|
909
|
+
return {
|
|
910
|
+
fail: (t) => {
|
|
911
|
+
context.actions.push({
|
|
912
|
+
type: "fail",
|
|
913
|
+
timestamp: Date.now(),
|
|
914
|
+
task: t
|
|
915
|
+
});
|
|
916
|
+
logger$2.error(`[${this.taskRunnerId}] Task failed: ${tId(t)} (${t.type})`);
|
|
917
|
+
},
|
|
918
|
+
success: (t) => {
|
|
919
|
+
context.actions.push({
|
|
920
|
+
type: "success",
|
|
921
|
+
timestamp: Date.now(),
|
|
922
|
+
task: t
|
|
923
|
+
});
|
|
924
|
+
logger$2.info(`[${this.taskRunnerId}] Task succeeded: ${tId(t)} (${t.type})`);
|
|
925
|
+
},
|
|
926
|
+
addTasks: (tasks) => {
|
|
927
|
+
context.actions.push({
|
|
928
|
+
type: "addTasks",
|
|
929
|
+
timestamp: Date.now(),
|
|
930
|
+
newTasks: tasks
|
|
931
|
+
});
|
|
932
|
+
logger$2.info(`[${this.taskRunnerId}] Task ${taskId} adding ${tasks.length} new tasks`);
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
// For multi-task executors - they use the root Actions directly (no forking)
|
|
937
|
+
fail(task) {
|
|
938
|
+
const taskId = tId(task);
|
|
939
|
+
let context = this.taskContexts.get(taskId);
|
|
940
|
+
if (!context) {
|
|
941
|
+
context = { task, actions: [] };
|
|
942
|
+
this.taskContexts.set(taskId, context);
|
|
943
|
+
}
|
|
944
|
+
context.actions.push({
|
|
945
|
+
type: "fail",
|
|
946
|
+
timestamp: Date.now(),
|
|
947
|
+
task
|
|
948
|
+
});
|
|
949
|
+
logger$2.error(`[${this.taskRunnerId}] Task failed: ${taskId} (${task.type})`);
|
|
950
|
+
}
|
|
951
|
+
success(task) {
|
|
952
|
+
const taskId = tId(task);
|
|
953
|
+
let context = this.taskContexts.get(taskId);
|
|
954
|
+
if (!context) {
|
|
955
|
+
context = { task, actions: [] };
|
|
956
|
+
this.taskContexts.set(taskId, context);
|
|
957
|
+
}
|
|
958
|
+
context.actions.push({
|
|
959
|
+
type: "success",
|
|
960
|
+
timestamp: Date.now(),
|
|
961
|
+
task
|
|
962
|
+
});
|
|
963
|
+
logger$2.info(`[${this.taskRunnerId}] Task succeeded: ${taskId} (${task.type})`);
|
|
964
|
+
}
|
|
965
|
+
addTasks(tasks) {
|
|
966
|
+
logger$2.info(`[${this.taskRunnerId}] Adding ${tasks.length} new tasks`);
|
|
967
|
+
const batchKey = `__batch_${this.taskRunnerId}__`;
|
|
968
|
+
let batchContext = this.taskContexts.get(batchKey);
|
|
969
|
+
if (!batchContext) {
|
|
970
|
+
batchContext = { task: null, actions: [] };
|
|
971
|
+
this.taskContexts.set(batchKey, batchContext);
|
|
972
|
+
}
|
|
973
|
+
batchContext.actions.push({
|
|
974
|
+
type: "addTasks",
|
|
975
|
+
timestamp: Date.now(),
|
|
976
|
+
newTasks: tasks
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
addIgnoredTask(task) {
|
|
980
|
+
const taskId = tId(task);
|
|
981
|
+
this.taskContexts.set(taskId, { task, actions: [] });
|
|
982
|
+
logger$2.warn(`[${this.taskRunnerId}] Task ignored: ${taskId} (${task.type})`);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Extract actions for a single task (used by async tasks)
|
|
986
|
+
*/
|
|
987
|
+
extractTaskActions(taskId) {
|
|
988
|
+
const results = {
|
|
989
|
+
failedTasks: [],
|
|
990
|
+
successTasks: [],
|
|
991
|
+
newTasks: [],
|
|
992
|
+
ignoredTasks: []
|
|
993
|
+
};
|
|
994
|
+
const context = this.taskContexts.get(taskId);
|
|
995
|
+
if (!context) return results;
|
|
996
|
+
if (context.actions.length === 0 && context.task) {
|
|
997
|
+
results.ignoredTasks.push(context.task);
|
|
998
|
+
} else {
|
|
999
|
+
for (const action of context.actions) {
|
|
1000
|
+
if (action.type === "success" && action.task) {
|
|
1001
|
+
results.successTasks.push(action.task);
|
|
1002
|
+
const targetTaskId = tId(action.task);
|
|
1003
|
+
if (targetTaskId !== taskId) {
|
|
1004
|
+
this.taskContexts.delete(targetTaskId);
|
|
1005
|
+
}
|
|
1006
|
+
} else if (action.type === "fail" && action.task) {
|
|
1007
|
+
results.failedTasks.push(action.task);
|
|
1008
|
+
const targetTaskId = tId(action.task);
|
|
1009
|
+
if (targetTaskId !== taskId) {
|
|
1010
|
+
this.taskContexts.delete(targetTaskId);
|
|
1011
|
+
}
|
|
1012
|
+
} else if (action.type === "addTasks" && action.newTasks) {
|
|
1013
|
+
results.newTasks.push(...action.newTasks);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
this.taskContexts.delete(taskId);
|
|
1018
|
+
return results;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Extract sync results including batch context (for sync processing)
|
|
1022
|
+
*/
|
|
1023
|
+
extractSyncResults(excludeTaskIds) {
|
|
1024
|
+
const results = {
|
|
1025
|
+
failedTasks: [],
|
|
1026
|
+
successTasks: [],
|
|
1027
|
+
newTasks: [],
|
|
1028
|
+
ignoredTasks: []
|
|
1029
|
+
};
|
|
1030
|
+
const excludeSet = new Set(excludeTaskIds);
|
|
1031
|
+
const batchKey = `__batch_${this.taskRunnerId}__`;
|
|
1032
|
+
for (const [taskId, context] of this.taskContexts) {
|
|
1033
|
+
if (excludeSet.has(taskId)) continue;
|
|
1034
|
+
if (taskId === batchKey) {
|
|
1035
|
+
for (const action of context.actions) {
|
|
1036
|
+
if (action.type === "addTasks" && action.newTasks) {
|
|
1037
|
+
results.newTasks.push(...action.newTasks);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
} else {
|
|
1041
|
+
if (context.actions.length === 0 && context.task) {
|
|
1042
|
+
results.ignoredTasks.push(context.task);
|
|
1043
|
+
} else {
|
|
1044
|
+
for (const action of context.actions) {
|
|
1045
|
+
if (action.type === "success" && action.task) {
|
|
1046
|
+
results.successTasks.push(action.task);
|
|
1047
|
+
} else if (action.type === "fail" && action.task) {
|
|
1048
|
+
results.failedTasks.push(action.task);
|
|
1049
|
+
} else if (action.type === "addTasks" && action.newTasks) {
|
|
1050
|
+
results.newTasks.push(...action.newTasks);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
for (const [taskId] of this.taskContexts) {
|
|
1057
|
+
if (!excludeSet.has(taskId)) {
|
|
1058
|
+
this.taskContexts.delete(taskId);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return results;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Get all results (mainly for backward compatibility)
|
|
1065
|
+
*/
|
|
1066
|
+
getResults() {
|
|
1067
|
+
return this.extractSyncResults([]);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const logger$1 = new Logger("AsyncActions", LogLevel.INFO);
|
|
1071
|
+
class AsyncActions {
|
|
1072
|
+
constructor(messageQueue, taskStore, taskQueue, actions, task, generateId) {
|
|
1073
|
+
this.messageQueue = messageQueue;
|
|
1074
|
+
this.taskStore = taskStore;
|
|
1075
|
+
this.taskQueue = taskQueue;
|
|
1076
|
+
this.generateId = generateId;
|
|
1077
|
+
this.actions = actions;
|
|
1078
|
+
this.taskId = tId(task);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Called when the async promise completes to execute the collected actions
|
|
1082
|
+
*/
|
|
1083
|
+
async onPromiseFulfilled() {
|
|
1084
|
+
const results = this.actions.extractTaskActions(this.taskId);
|
|
1085
|
+
const hasCompletion = results.successTasks.length > 0 || results.failedTasks.length > 0;
|
|
1086
|
+
if (!hasCompletion) {
|
|
1087
|
+
throw new Error(`Async task ${this.taskId} completed without calling success() or fail()`);
|
|
1088
|
+
}
|
|
1089
|
+
logger$1.info(`[AsyncActions] Processing results for async task ${this.taskId}: ${results.successTasks.length} success, ${results.failedTasks.length} failed, ${results.newTasks.length} new tasks`);
|
|
1090
|
+
if (results.failedTasks.length > 0) {
|
|
1091
|
+
try {
|
|
1092
|
+
const failedTaskIds = results.failedTasks.map((task) => task._id);
|
|
1093
|
+
await this.taskStore.markTasksAsFailed(failedTaskIds);
|
|
1094
|
+
logger$1.info(`[AsyncActions] Marked ${results.failedTasks.length} tasks as failed in database`);
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
logger$1.error(`[AsyncActions] Failed to mark tasks as failed:`, err);
|
|
1097
|
+
throw err;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (results.successTasks.length > 0) {
|
|
1101
|
+
try {
|
|
1102
|
+
await this.taskStore.markTasksAsSuccess(results.successTasks);
|
|
1103
|
+
logger$1.info(`[AsyncActions] Marked ${results.successTasks.length} tasks as success in database`);
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
logger$1.error(`[AsyncActions] Failed to mark tasks as success:`, err);
|
|
1106
|
+
throw err;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (results.newTasks.length > 0) {
|
|
1110
|
+
logger$1.info(`[AsyncActions] Scheduling ${results.newTasks.length} new tasks`);
|
|
1111
|
+
await this.scheduleNewTasks(results.newTasks);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Schedule new tasks - replicates the logic from task-handler's addTasks
|
|
1116
|
+
*/
|
|
1117
|
+
async scheduleNewTasks(tasks) {
|
|
1118
|
+
const now = /* @__PURE__ */ new Date();
|
|
1119
|
+
const immediate = {};
|
|
1120
|
+
const future = [];
|
|
1121
|
+
for (const task of tasks) {
|
|
1122
|
+
const timeDiff = (task.execute_at.getTime() - now.getTime()) / 1e3 / 60;
|
|
1123
|
+
if (timeDiff > 2) {
|
|
1124
|
+
future.push(task);
|
|
1125
|
+
} else {
|
|
1126
|
+
const queue = task.queue_id;
|
|
1127
|
+
if (!immediate[queue]) {
|
|
1128
|
+
immediate[queue] = [];
|
|
1129
|
+
}
|
|
1130
|
+
immediate[queue].push(task);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const iQueues = Object.keys(immediate);
|
|
1134
|
+
for (const queue of iQueues) {
|
|
1135
|
+
const queueTasks = immediate[queue].map((task) => {
|
|
1136
|
+
const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
|
|
1137
|
+
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
1138
|
+
const id = shouldStoreOnFailure ? { _id: this.generateId() } : {};
|
|
1139
|
+
return { ...id, ...task };
|
|
1140
|
+
});
|
|
1141
|
+
try {
|
|
1142
|
+
await this.messageQueue.addMessages(queue, queueTasks);
|
|
1143
|
+
logger$1.info(`[AsyncActions] Added ${queueTasks.length} immediate tasks to queue ${queue}`);
|
|
1144
|
+
} catch (err) {
|
|
1145
|
+
logger$1.error(`[AsyncActions] Failed to add tasks to queue ${queue}:`, err);
|
|
1146
|
+
throw err;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (future.length > 0) {
|
|
1150
|
+
const futureTasks = future.map((task) => {
|
|
1151
|
+
const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
|
|
1152
|
+
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
1153
|
+
const id = shouldStoreOnFailure ? { _id: this.generateId() } : {};
|
|
1154
|
+
return { ...id, ...task };
|
|
1155
|
+
});
|
|
1156
|
+
try {
|
|
1157
|
+
await this.taskStore.addTasksToScheduled(futureTasks);
|
|
1158
|
+
logger$1.info(`[AsyncActions] Added ${futureTasks.length} future tasks to database`);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
logger$1.error(`[AsyncActions] Failed to add tasks to database:`, err);
|
|
1161
|
+
throw err;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
class TaskRunner {
|
|
1167
|
+
constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId) {
|
|
1168
|
+
this.messageQueue = messageQueue;
|
|
1169
|
+
this.taskQueue = taskQueue;
|
|
1170
|
+
this.taskStore = taskStore;
|
|
1171
|
+
this.generateId = generateId;
|
|
1172
|
+
this.logger = new Logger("TaskRunner", LogLevel.INFO);
|
|
1173
|
+
this.lockManager = new LockManager(cacheProvider, {
|
|
1174
|
+
prefix: "task_lock_",
|
|
1175
|
+
defaultTimeout: 30 * 60
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
async run(taskRunnerId, tasksRaw, asyncTaskManager) {
|
|
1179
|
+
this.logger.info(`[${taskRunnerId}] Starting task runner`);
|
|
1180
|
+
this.logger.info(`[${taskRunnerId}] Processing ${tasksRaw.length} provided tasks`);
|
|
1181
|
+
const tasks = await this.lockManager.filterLocked(tasksRaw, tId);
|
|
1182
|
+
this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
|
|
1183
|
+
await Promise.all(tasks.map((t) => this.lockManager.acquire(tId(t))));
|
|
1184
|
+
const groupedTasksObject = tasks.reduce((acc, task) => {
|
|
1185
|
+
acc[task.type] = acc[task.type] || [];
|
|
1186
|
+
acc[task.type].push(task);
|
|
1187
|
+
return acc;
|
|
1188
|
+
}, {});
|
|
1189
|
+
const groupedTasksArray = Object.keys(groupedTasksObject).reduce((acc, type) => {
|
|
1190
|
+
acc.push({ type, tasks: groupedTasksObject[type] });
|
|
1191
|
+
return acc;
|
|
1192
|
+
}, []);
|
|
1193
|
+
this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
|
|
1194
|
+
const actions = new Actions(taskRunnerId);
|
|
1195
|
+
const asyncTasks = [];
|
|
1196
|
+
for (let i = 0; i < groupedTasksArray.length; i++) {
|
|
1197
|
+
const taskGroup = groupedTasksArray[i];
|
|
1198
|
+
const firstTask = taskGroup.tasks[0];
|
|
1199
|
+
if (!firstTask) {
|
|
1200
|
+
this.logger.warn(`[${taskRunnerId}] No tasks found for type: ${taskGroup.type}`);
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const executor = this.taskQueue.getExecutor(firstTask.queue_id, taskGroup.type);
|
|
1204
|
+
if (!executor) {
|
|
1205
|
+
this.logger.warn(`[${taskRunnerId}] No executor found for type: ${taskGroup.type} in queue ${firstTask.queue_id}`);
|
|
1206
|
+
for (const task of taskGroup.tasks) {
|
|
1207
|
+
const taskWithId = task._id ? task : { ...task, _id: "" };
|
|
1208
|
+
actions.addIgnoredTask(taskWithId);
|
|
1209
|
+
}
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (executor.asyncConfig?.handoffTimeout && asyncTaskManager && !asyncTaskManager.canAcceptTask()) {
|
|
1213
|
+
this.logger.warn(`[${taskRunnerId}] Async queue full, rescheduling ${taskGroup.tasks.length} ${taskGroup.type} tasks for 3 min later`);
|
|
1214
|
+
const rescheduledTasks = taskGroup.tasks.map((task) => ({
|
|
1215
|
+
...task,
|
|
1216
|
+
execute_at: new Date(Date.now() + 18e4),
|
|
1217
|
+
status: "scheduled"
|
|
1218
|
+
}));
|
|
1219
|
+
await this.taskStore.updateTasksForRetry(rescheduledTasks);
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
1223
|
+
if (executor.multiple) {
|
|
1224
|
+
await executor.onTasks(taskGroup.tasks, actions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`));
|
|
1225
|
+
} else {
|
|
1226
|
+
if (executor.parallel) {
|
|
1227
|
+
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
1228
|
+
this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
|
|
1229
|
+
for (const chunk2 of chunks) {
|
|
1230
|
+
const chunkTasks = [];
|
|
1231
|
+
for (let j = 0; j < chunk2.length; j++) {
|
|
1232
|
+
const taskActions = actions.forkForTask(chunk2[j]);
|
|
1233
|
+
chunkTasks.push(executor.onTask(chunk2[j], taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`)));
|
|
1234
|
+
}
|
|
1235
|
+
await Promise.all(chunkTasks);
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
const timeoutMs = executor.asyncConfig?.handoffTimeout;
|
|
1239
|
+
for (let j = 0; j < taskGroup.tasks.length; j++) {
|
|
1240
|
+
const task = taskGroup.tasks[j];
|
|
1241
|
+
if (!timeoutMs) {
|
|
1242
|
+
const taskActions = actions.forkForTask(task);
|
|
1243
|
+
await executor.onTask(task, taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`));
|
|
1244
|
+
} else {
|
|
1245
|
+
const startTime = Date.now();
|
|
1246
|
+
const taskActions = actions.forkForTask(task);
|
|
1247
|
+
const taskPromise = executor.onTask(task, taskActions).catch((err) => {
|
|
1248
|
+
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
1249
|
+
});
|
|
1250
|
+
const timeoutPromise = new Promise(
|
|
1251
|
+
(resolve) => setTimeout(() => {
|
|
1252
|
+
resolve("~~~timeout");
|
|
1253
|
+
}, timeoutMs)
|
|
1254
|
+
);
|
|
1255
|
+
const result = await Promise.race([taskPromise, timeoutPromise]);
|
|
1256
|
+
if (result === "~~~timeout") {
|
|
1257
|
+
this.logger.info(`[${taskRunnerId}] Task ${tId(task)} (${task.type}) exceeded ${timeoutMs}ms, marking for async handoff`);
|
|
1258
|
+
if (!asyncTaskManager) {
|
|
1259
|
+
throw new Error(`Task ${task.type} exceeded timeout but AsyncTaskManager not initialized!`);
|
|
1260
|
+
}
|
|
1261
|
+
if (!task._id) {
|
|
1262
|
+
this.logger.error(`[${taskRunnerId}] Cannot hand off task without _id (type: ${task.type}). Task will continue but won't be tracked.`);
|
|
1263
|
+
} else {
|
|
1264
|
+
const asyncActions = new AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId);
|
|
1265
|
+
const asyncPromise = taskPromise.finally(async () => {
|
|
1266
|
+
try {
|
|
1267
|
+
await asyncActions.onPromiseFulfilled();
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
this.logger.error(`[${taskRunnerId}] Failed to execute async actions for task ${tId(task)}:`, err);
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
asyncTasks.push({
|
|
1273
|
+
task,
|
|
1274
|
+
promise: asyncPromise,
|
|
1275
|
+
startTime,
|
|
1276
|
+
actions: asyncActions
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
const asyncTaskIds = asyncTasks.map((at) => tId(at.task));
|
|
1286
|
+
const results = actions.extractSyncResults(asyncTaskIds);
|
|
1287
|
+
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}`);
|
|
1288
|
+
await Promise.all(tasks.map((t) => this.lockManager.release(tId(t))));
|
|
1289
|
+
return { ...results, asyncTasks };
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
function getEnabledQueues() {
|
|
1293
|
+
let enabledQueues = process.env.ENABLED_QUEUES ? JSON.parse(process.env.ENABLED_QUEUES) : [];
|
|
1294
|
+
if (enabledQueues.length === 0) throw new Error("No queues enabled");
|
|
1295
|
+
return enabledQueues.map(mq.getEnvironmentQueueName);
|
|
1296
|
+
}
|
|
1297
|
+
const METRICS_KEY_PREFIX = "task_metrics:";
|
|
1298
|
+
const DISCARDED_TASKS_KEY = `${METRICS_KEY_PREFIX}discarded_tasks`;
|
|
1299
|
+
const STATS_THRESHOLD = parseInt(process.env.TQ_STATS_THRESHOLD || "1000");
|
|
1300
|
+
const FAILURE_THRESHOLD = parseInt(process.env.TQ_STATS_FAILURE_THRESHOLD || "100");
|
|
1301
|
+
const INSTANCE_ID = process.env.INSTANCE_ID || "unknown";
|
|
1302
|
+
const slack = { sendSlackMessage: console.log };
|
|
1303
|
+
class TaskHandler {
|
|
1304
|
+
constructor(messageQueue, taskQueuesManager, databaseAdapter, cacheAdapter, asyncTaskManager) {
|
|
1305
|
+
this.messageQueue = messageQueue;
|
|
1306
|
+
this.taskQueuesManager = taskQueuesManager;
|
|
1307
|
+
this.databaseAdapter = databaseAdapter;
|
|
1308
|
+
this.cacheAdapter = cacheAdapter;
|
|
1309
|
+
this.asyncTaskManager = asyncTaskManager;
|
|
1310
|
+
this.matureTaskTimer = null;
|
|
1311
|
+
this.queueStats = /* @__PURE__ */ new Map();
|
|
1312
|
+
this.logger = new Logger("TaskHandler", LogLevel.INFO);
|
|
1313
|
+
this.taskStore = new TaskStore(databaseAdapter);
|
|
1314
|
+
this.taskRunner = new TaskRunner(messageQueue, taskQueuesManager, this.taskStore, this.cacheAdapter, databaseAdapter.generateId.bind(databaseAdapter));
|
|
1315
|
+
}
|
|
1316
|
+
async addTasks(tasks) {
|
|
1317
|
+
const diffedItems = tasks.reduce(
|
|
1318
|
+
(acc, { force_store, ...task }) => {
|
|
1319
|
+
const currentTime = /* @__PURE__ */ new Date();
|
|
1320
|
+
const executeTime = task.execute_at;
|
|
1321
|
+
const timeDifference = (executeTime.getTime() - currentTime.getTime()) / 1e3 / 60;
|
|
1322
|
+
const queue = task.queue_id;
|
|
1323
|
+
if (timeDifference > 2 || force_store) {
|
|
1324
|
+
acc.future[queue] = acc.future[queue] || [];
|
|
1325
|
+
acc.future[queue].push(task);
|
|
1326
|
+
} else {
|
|
1327
|
+
acc.immediate[queue] = acc.immediate[queue] || [];
|
|
1328
|
+
acc.immediate[queue].push(task);
|
|
1329
|
+
}
|
|
1330
|
+
return acc;
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
future: {},
|
|
1334
|
+
immediate: {}
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
const iQueues = Object.keys(diffedItems.immediate);
|
|
1338
|
+
for (let i = 0; i < iQueues.length; i++) {
|
|
1339
|
+
const queue = iQueues[i];
|
|
1340
|
+
const tasks2 = diffedItems.immediate[queue].map((task) => {
|
|
1341
|
+
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
1342
|
+
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
1343
|
+
const id = shouldStoreOnFailure ? { _id: this.databaseAdapter.generateId() } : {};
|
|
1344
|
+
return { ...id, ...task };
|
|
1345
|
+
});
|
|
1346
|
+
await this.messageQueue.addMessages(queue, tasks2);
|
|
1347
|
+
}
|
|
1348
|
+
const fQueues = Object.keys(diffedItems.future);
|
|
1349
|
+
for (let i = 0; i < fQueues.length; i++) {
|
|
1350
|
+
const queue = fQueues[i];
|
|
1351
|
+
const tasks2 = diffedItems.future[queue].map((task) => {
|
|
1352
|
+
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
1353
|
+
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
1354
|
+
const id = shouldStoreOnFailure ? { _id: this.databaseAdapter.generateId() } : {};
|
|
1355
|
+
return { ...id, ...task };
|
|
1356
|
+
});
|
|
1357
|
+
await this.taskStore.addTasksToScheduled(tasks2);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
async postProcessTasks({
|
|
1361
|
+
failedTasks: failedTasksRaw,
|
|
1362
|
+
newTasks,
|
|
1363
|
+
successTasks
|
|
1364
|
+
}) {
|
|
1365
|
+
const tasksToRetry = [];
|
|
1366
|
+
const finalFailedTasks = [];
|
|
1367
|
+
let discardedTasksCount = 0;
|
|
1368
|
+
for (const task of failedTasksRaw) {
|
|
1369
|
+
const taskRetryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
1370
|
+
const taskRetryAfter = task.retry_after || 2e3;
|
|
1371
|
+
const retryAfter = taskRetryAfter * Math.pow(taskRetryCount + 1, 2);
|
|
1372
|
+
const executeAt = Date.now() + retryAfter;
|
|
1373
|
+
const maxRetries = this.getRetryCount(task);
|
|
1374
|
+
if (task._id && taskRetryCount < maxRetries) {
|
|
1375
|
+
tasksToRetry.push({
|
|
1376
|
+
...task,
|
|
1377
|
+
status: "scheduled",
|
|
1378
|
+
execute_at: new Date(executeAt),
|
|
1379
|
+
execution_stats: {
|
|
1380
|
+
...task.execution_stats || {},
|
|
1381
|
+
retry_count: taskRetryCount + 1
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
} else if (task._id) {
|
|
1385
|
+
finalFailedTasks.push(task);
|
|
1386
|
+
} else if (taskRetryCount < maxRetries) {
|
|
1387
|
+
const shouldStoreOnFailure = this.taskQueuesManager.getExecutor(task.queue_id, task.type)?.store_on_failure;
|
|
1388
|
+
if (shouldStoreOnFailure) {
|
|
1389
|
+
tasksToRetry.push({
|
|
1390
|
+
...task,
|
|
1391
|
+
_id: this.databaseAdapter.generateId(),
|
|
1392
|
+
status: "scheduled",
|
|
1393
|
+
execute_at: new Date(executeAt),
|
|
1394
|
+
execution_stats: {
|
|
1395
|
+
...task.execution_stats || {},
|
|
1396
|
+
retry_count: taskRetryCount + 1
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
} else {
|
|
1400
|
+
await this.addTasks([{
|
|
1401
|
+
...task,
|
|
1402
|
+
status: "scheduled",
|
|
1403
|
+
execute_at: new Date(executeAt),
|
|
1404
|
+
execution_stats: {
|
|
1405
|
+
...task.execution_stats || {},
|
|
1406
|
+
retry_count: taskRetryCount + 1
|
|
1407
|
+
}
|
|
1408
|
+
}]);
|
|
1409
|
+
}
|
|
1410
|
+
} else {
|
|
1411
|
+
discardedTasksCount++;
|
|
1412
|
+
this.logger.info(`Discarding task of type ${task.type} after ${taskRetryCount} retries`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (discardedTasksCount > 0) {
|
|
1416
|
+
await this.trackDiscardedTasks(discardedTasksCount);
|
|
1417
|
+
}
|
|
1418
|
+
if (tasksToRetry.length > 0) {
|
|
1419
|
+
await this.taskStore.updateTasksForRetry(tasksToRetry);
|
|
1420
|
+
}
|
|
1421
|
+
if (newTasks.length > 0) {
|
|
1422
|
+
await this.addTasks(newTasks);
|
|
1423
|
+
}
|
|
1424
|
+
if (finalFailedTasks.length > 0) {
|
|
1425
|
+
const taskIds = finalFailedTasks.map((task) => task._id);
|
|
1426
|
+
await this.taskStore.markTasksAsFailed(taskIds);
|
|
1427
|
+
}
|
|
1428
|
+
if (successTasks.length > 0) {
|
|
1429
|
+
await this.taskStore.markTasksAsSuccess(successTasks);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
startConsumingTasks(streamName) {
|
|
1433
|
+
return this.messageQueue.consumeMessagesStream(streamName, async (id, tasks) => {
|
|
1434
|
+
this.logger.debug(`Processing ${tasks.length} tasks for stream ${streamName}`);
|
|
1435
|
+
const {
|
|
1436
|
+
failedTasks,
|
|
1437
|
+
newTasks,
|
|
1438
|
+
successTasks,
|
|
1439
|
+
asyncTasks,
|
|
1440
|
+
ignoredTasks
|
|
1441
|
+
} = await this.taskRunner.run(id, tasks, this.asyncTaskManager).catch((err) => {
|
|
1442
|
+
this.logger.error("Failed to execute tasks?", err);
|
|
1443
|
+
return { failedTasks: [], newTasks: [], successTasks: [], asyncTasks: [], ignoredTasks: [] };
|
|
1444
|
+
});
|
|
1445
|
+
if (asyncTasks.length > 0 && !this.asyncTaskManager) {
|
|
1446
|
+
throw new Error("Async tasks detected but AsyncTaskManager not initialized!");
|
|
1447
|
+
}
|
|
1448
|
+
if (asyncTasks.length > 0) {
|
|
1449
|
+
this.logger.info(`Handling ${asyncTasks.length} async tasks for stream ${streamName}`);
|
|
1450
|
+
for (const asyncTask of asyncTasks) {
|
|
1451
|
+
const accepted = this.asyncTaskManager.handoffTask(asyncTask.task, asyncTask.promise);
|
|
1452
|
+
if (!accepted) {
|
|
1453
|
+
this.logger.warn(`Async queue full, requeueing task ${asyncTask.task._id} with 30s delay`);
|
|
1454
|
+
await this.addTasks([{
|
|
1455
|
+
...asyncTask.task,
|
|
1456
|
+
execute_at: new Date(Date.now() + 3e4)
|
|
1457
|
+
}]);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
if (ignoredTasks.length > 0) {
|
|
1462
|
+
this.logger.warn(`Storing ${ignoredTasks.length} ignored tasks with no executor for stream ${streamName}`);
|
|
1463
|
+
await this.taskStore.markTasksAsIgnored(ignoredTasks).catch((err) => {
|
|
1464
|
+
this.logger.error("Failed to mark tasks as ignored", err);
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
await this.postProcessTasks({ failedTasks, newTasks, successTasks }).catch((err) => {
|
|
1468
|
+
this.logger.error("Failed to postProcessTasks", err);
|
|
1469
|
+
});
|
|
1470
|
+
if (!this.queueStats.has(streamName)) {
|
|
1471
|
+
this.queueStats.set(streamName, {
|
|
1472
|
+
success: 0,
|
|
1473
|
+
failed: 0,
|
|
1474
|
+
async: 0,
|
|
1475
|
+
ignored: 0
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
const stats = this.queueStats.get(streamName);
|
|
1479
|
+
stats.success += successTasks.length;
|
|
1480
|
+
stats.failed += failedTasks.length;
|
|
1481
|
+
stats.async += asyncTasks.length;
|
|
1482
|
+
stats.ignored += ignoredTasks.length;
|
|
1483
|
+
await this.reportQueueStats(streamName);
|
|
1484
|
+
this.logger.debug(`Completed processing for stream ${streamName}: ${successTasks.length} succeeded, ${failedTasks.length} failed, ${newTasks.length} new tasks, ${ignoredTasks.length} ignored`);
|
|
1485
|
+
return { failedTasks, newTasks, successTasks, asyncTasks, ignoredTasks };
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
processMatureTasks() {
|
|
1489
|
+
if (this.matureTaskTimer) clearInterval(this.matureTaskTimer);
|
|
1490
|
+
this.matureTaskTimer = setInterval(async () => {
|
|
1491
|
+
const lockManager = new LockManager(this.cacheAdapter, { prefix: "mature_task_lock_:" });
|
|
1492
|
+
if (await lockManager.isLocked("task_processor")) {
|
|
1493
|
+
this.logger.info("Mature task runner locked");
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
try {
|
|
1497
|
+
const acquired = await lockManager.acquire("task_processor", 20);
|
|
1498
|
+
if (!acquired) {
|
|
1499
|
+
this.logger.info("Could not acquire lock for mature task processing");
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const matureTasks = await this.taskStore.getMatureTasks(Date.now());
|
|
1503
|
+
this.logger.debug(`Found ${matureTasks.length} mature tasks to process`);
|
|
1504
|
+
await this.addTasks(matureTasks);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
this.logger.error(`Error processing tasks: ${error}`);
|
|
1507
|
+
} finally {
|
|
1508
|
+
await lockManager.release("task_processor");
|
|
1509
|
+
}
|
|
1510
|
+
}, 5e3);
|
|
1511
|
+
}
|
|
1512
|
+
taskProcessServer() {
|
|
1513
|
+
const queues = getEnabledQueues();
|
|
1514
|
+
for (let i = 0; i < queues.length; i++) {
|
|
1515
|
+
this.logger.info(`Starting consumer for queue: ${queues[i]}`);
|
|
1516
|
+
this.startConsumingTasks(queues[i]);
|
|
1517
|
+
}
|
|
1518
|
+
this.logger.info("Starting mature tasks processor");
|
|
1519
|
+
this.processMatureTasks();
|
|
1520
|
+
}
|
|
1521
|
+
processBatch(queueId, processor, limit) {
|
|
1522
|
+
return this.messageQueue.consumeMessagesBatch(queueId, processor, limit);
|
|
1523
|
+
}
|
|
1524
|
+
async trackDiscardedTasks(count) {
|
|
1525
|
+
try {
|
|
1526
|
+
const now = moment.utc();
|
|
1527
|
+
const hourKey = `${DISCARDED_TASKS_KEY}:${now.format("YYYY-MM-DD-HH")}`;
|
|
1528
|
+
if (!this.cacheAdapter) throw new Error("Cache adapter not initialized");
|
|
1529
|
+
const currentHourCountStr = await this.cacheAdapter.get(hourKey) || "0";
|
|
1530
|
+
const currentHourCount = parseInt(currentHourCountStr, 10);
|
|
1531
|
+
const newCount = currentHourCount + count;
|
|
1532
|
+
await this.cacheAdapter.set(hourKey, newCount.toString(), 25 * 3600);
|
|
1533
|
+
let total = 0;
|
|
1534
|
+
try {
|
|
1535
|
+
if (Math.random() < 0.1) {
|
|
1536
|
+
for (let i = 0; i < 24; i++) {
|
|
1537
|
+
const hourOffset = moment.utc().subtract(i, "hours");
|
|
1538
|
+
const pastHourKey = `${DISCARDED_TASKS_KEY}:${hourOffset.format("YYYY-MM-DD-HH")}`;
|
|
1539
|
+
const pastHourCountStr = await this.cacheAdapter.get(pastHourKey) || "0";
|
|
1540
|
+
total += parseInt(pastHourCountStr, 10);
|
|
1541
|
+
}
|
|
1542
|
+
this.logger.info(`Added ${count} discarded tasks to metrics. Last 24h total: ${total}`);
|
|
1543
|
+
} else {
|
|
1544
|
+
this.logger.info(`Added ${count} discarded tasks to current hour metrics.`);
|
|
1545
|
+
}
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
this.logger.info(`Added ${count} discarded tasks to current hour metrics.`);
|
|
1548
|
+
}
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
this.logger.error(`Failed to track discarded tasks: ${error}`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
getRetryCount(task) {
|
|
1554
|
+
if (typeof task.retries === "number") return task.retries;
|
|
1555
|
+
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
1556
|
+
return executor?.default_retries ?? 3;
|
|
1557
|
+
}
|
|
1558
|
+
async reportQueueStats(queueName, forceSend = false) {
|
|
1559
|
+
const stats = this.queueStats.get(queueName);
|
|
1560
|
+
if (!stats) return;
|
|
1561
|
+
const total = stats.success + stats.failed + stats.async + stats.ignored;
|
|
1562
|
+
if (total === 0) return;
|
|
1563
|
+
if (!forceSend && total < STATS_THRESHOLD && stats.failed < FAILURE_THRESHOLD) return;
|
|
1564
|
+
`[${INSTANCE_ID}] TQ Stats for ${queueName}:
|
|
1565
|
+
✅ Success: ${stats.success}
|
|
1566
|
+
❌ Failed: ${stats.failed}
|
|
1567
|
+
⏳ Async: ${stats.async}
|
|
1568
|
+
🚫 Ignored: ${stats.ignored}`;
|
|
1569
|
+
try {
|
|
1570
|
+
this.logger.info(`Sent stats for ${queueName}`);
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
this.logger.error(`Failed to send stats: ${err}`);
|
|
1573
|
+
}
|
|
1574
|
+
if (!forceSend) {
|
|
1575
|
+
stats.success = 0;
|
|
1576
|
+
stats.failed = 0;
|
|
1577
|
+
stats.async = 0;
|
|
1578
|
+
stats.ignored = 0;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
async sendFinalStats() {
|
|
1582
|
+
for (const queueName of this.queueStats.keys()) {
|
|
1583
|
+
await this.reportQueueStats(queueName, true);
|
|
1584
|
+
}
|
|
1585
|
+
if (this.queueStats.size === 0) {
|
|
1586
|
+
try {
|
|
1587
|
+
const message = `[${INSTANCE_ID}] TQ Final Stats:
|
|
1588
|
+
No tasks processed during this session`;
|
|
1589
|
+
await slack.sendSlackMessage(void 0, message);
|
|
1590
|
+
this.logger.info("Sent final TQ stats to Slack (no tasks)");
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
this.logger.error(`Failed to send final stats: ${err}`);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
class TaskQueuesManager {
|
|
1598
|
+
constructor(messageQueue) {
|
|
1599
|
+
this.messageQueue = messageQueue;
|
|
1600
|
+
this.queueTaskExecutorMap = /* @__PURE__ */ new Map();
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Registers a task executor with a message queue and queue name
|
|
1604
|
+
* @param queueName The name of the queue
|
|
1605
|
+
* @param taskType The type of task
|
|
1606
|
+
* @param executor The executor for the task
|
|
1607
|
+
*/
|
|
1608
|
+
register(queueName, taskType, executor) {
|
|
1609
|
+
queueName = mq.getEnvironmentQueueName(queueName);
|
|
1610
|
+
this.messageQueue.register(queueName);
|
|
1611
|
+
if (!this.queueTaskExecutorMap.has(queueName)) {
|
|
1612
|
+
this.queueTaskExecutorMap.set(queueName, /* @__PURE__ */ new Map());
|
|
1613
|
+
}
|
|
1614
|
+
const queueMap = this.queueTaskExecutorMap.get(queueName);
|
|
1615
|
+
queueMap.set(taskType, executor);
|
|
1616
|
+
console.log(`Registered task executor for ${taskType} on queue ${queueName}`);
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Gets the task executor for a specific queue and task type
|
|
1620
|
+
* @param queueName The name of the queue
|
|
1621
|
+
* @param taskType The type of task
|
|
1622
|
+
* @returns The executor for the task
|
|
1623
|
+
*/
|
|
1624
|
+
getExecutor(queueName, taskType) {
|
|
1625
|
+
queueName = mq.getEnvironmentQueueName(queueName);
|
|
1626
|
+
const queueMap = this.queueTaskExecutorMap.get(queueName);
|
|
1627
|
+
if (!queueMap) {
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
return queueMap.get(taskType);
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Gets all registered queues
|
|
1634
|
+
* @returns Array of queue names
|
|
1635
|
+
*/
|
|
1636
|
+
getQueues() {
|
|
1637
|
+
return Array.from(this.queueTaskExecutorMap.keys());
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Gets all task types for a specific queue
|
|
1641
|
+
* @param queueName The name of the queue
|
|
1642
|
+
* @returns Array of task types
|
|
1643
|
+
*/
|
|
1644
|
+
getTasksForQueue(queueName) {
|
|
1645
|
+
queueName = mq.getEnvironmentQueueName(queueName);
|
|
1646
|
+
const queueMap = this.queueTaskExecutorMap.get(queueName);
|
|
1647
|
+
if (!queueMap) {
|
|
1648
|
+
return [];
|
|
1649
|
+
}
|
|
1650
|
+
return Array.from(queueMap.keys());
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
const logger = new Logger("AsyncTaskManager", LogLevel.INFO);
|
|
1654
|
+
class AsyncTaskManager {
|
|
1655
|
+
constructor(maxTasks = 100) {
|
|
1656
|
+
this.asyncTasks = /* @__PURE__ */ new Map();
|
|
1657
|
+
this.handedOffTaskIds = /* @__PURE__ */ new Set();
|
|
1658
|
+
this.totalHandedOff = 0;
|
|
1659
|
+
this.maxTasks = maxTasks;
|
|
1660
|
+
logger.info(`AsyncTaskManager initialized with max ${maxTasks} concurrent tasks`);
|
|
1661
|
+
}
|
|
1662
|
+
handoffTask(task, runningPromise) {
|
|
1663
|
+
if (!task._id) {
|
|
1664
|
+
logger.error(`Cannot hand off task without _id (type: ${task.type})`);
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
const taskId = task._id.toString();
|
|
1668
|
+
if (this.asyncTasks.size >= this.maxTasks) {
|
|
1669
|
+
logger.warn(`Async queue full (${this.asyncTasks.size}/${this.maxTasks}), rejecting task ${taskId}`);
|
|
1670
|
+
return false;
|
|
1671
|
+
}
|
|
1672
|
+
const entry = {
|
|
1673
|
+
task,
|
|
1674
|
+
promise: runningPromise,
|
|
1675
|
+
startTime: Date.now()
|
|
1676
|
+
};
|
|
1677
|
+
this.asyncTasks.set(taskId, entry);
|
|
1678
|
+
this.handedOffTaskIds.add(taskId);
|
|
1679
|
+
this.totalHandedOff++;
|
|
1680
|
+
logger.info(`Task ${taskId} (${task.type}) handed off to async processing (queue: ${this.asyncTasks.size}/${this.maxTasks})`);
|
|
1681
|
+
runningPromise.then(() => {
|
|
1682
|
+
const duration = Date.now() - entry.startTime;
|
|
1683
|
+
logger.info(`Async task ${taskId} completed after ${duration}ms`);
|
|
1684
|
+
}).catch((error) => {
|
|
1685
|
+
const duration = Date.now() - entry.startTime;
|
|
1686
|
+
logger.error(`Async task ${taskId} errored after ${duration}ms:`, error);
|
|
1687
|
+
}).finally(() => {
|
|
1688
|
+
this.asyncTasks.delete(taskId);
|
|
1689
|
+
this.handedOffTaskIds.delete(taskId);
|
|
1690
|
+
logger.debug(`Task ${taskId} removed from async queue (remaining: ${this.asyncTasks.size})`);
|
|
1691
|
+
});
|
|
1692
|
+
return true;
|
|
1693
|
+
}
|
|
1694
|
+
async shutdown() {
|
|
1695
|
+
logger.info(`Shutting down AsyncTaskManager with ${this.asyncTasks.size} tasks still running`);
|
|
1696
|
+
const gracePeriod = 1e4;
|
|
1697
|
+
logger.info(`Waiting ${gracePeriod}ms for tasks to complete...`);
|
|
1698
|
+
await new Promise((resolve) => setTimeout(resolve, gracePeriod));
|
|
1699
|
+
if (this.asyncTasks.size > 0) {
|
|
1700
|
+
logger.warn(`${this.asyncTasks.size} tasks still running after grace period, they will continue in background`);
|
|
1701
|
+
for (const [taskId, entry] of this.asyncTasks) {
|
|
1702
|
+
const runtime = Date.now() - entry.startTime;
|
|
1703
|
+
logger.info(`Task ${taskId} (${entry.task.type}) has been running for ${runtime}ms`);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
logger.info("AsyncTaskManager shutdown complete");
|
|
1707
|
+
}
|
|
1708
|
+
getMetrics() {
|
|
1709
|
+
return {
|
|
1710
|
+
activeTaskCount: this.asyncTasks.size,
|
|
1711
|
+
totalHandedOff: this.totalHandedOff,
|
|
1712
|
+
maxTasks: this.maxTasks,
|
|
1713
|
+
utilizationPercent: this.asyncTasks.size / this.maxTasks * 100
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
isHandedOff(taskId) {
|
|
1717
|
+
return this.handedOffTaskIds.has(taskId);
|
|
1718
|
+
}
|
|
1719
|
+
canAcceptTask() {
|
|
1720
|
+
return this.asyncTasks.size < this.maxTasks;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
exports.Actions = Actions;
|
|
1724
|
+
exports.AsyncActions = AsyncActions;
|
|
1725
|
+
exports.AsyncTaskManager = AsyncTaskManager;
|
|
1726
|
+
exports.InMemoryAdapter = InMemoryAdapter;
|
|
1727
|
+
exports.MongoDbAdapter = MongoDbAdapter;
|
|
1728
|
+
exports.TaskHandler = TaskHandler;
|
|
1729
|
+
exports.TaskQueuesManager = TaskQueuesManager;
|
|
1730
|
+
exports.TaskRunner = TaskRunner;
|
|
1731
|
+
exports.TaskStore = TaskStore;
|
|
1732
|
+
exports.getEnabledQueues = getEnabledQueues;
|
|
1733
|
+
exports.setKeyGenerator = setKeyGenerator;
|
|
1734
|
+
//# sourceMappingURL=index.js.map
|