@tstdl/base 0.93.92 → 0.93.93
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/authentication/client/authentication.service.js +3 -2
- package/document-management/server/services/document-validation.service.js +5 -5
- package/document-management/server/services/document-workflow.service.js +2 -2
- package/orm/sqls/sqls.d.ts +6 -6
- package/package.json +2 -2
- package/task-queue/enqueue-batch.d.ts +16 -11
- package/task-queue/enqueue-batch.js +2 -2
- package/task-queue/index.d.ts +2 -1
- package/task-queue/index.js +2 -1
- package/task-queue/postgres/drizzle/{0000_thin_black_panther.sql → 0000_simple_invisible_woman.sql} +5 -5
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +11 -11
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.js +2 -2
- package/task-queue/postgres/schemas.d.ts +1 -1
- package/task-queue/postgres/schemas.js +2 -2
- package/task-queue/postgres/task-queue.d.ts +101 -47
- package/task-queue/postgres/task-queue.js +149 -139
- package/task-queue/postgres/task-queue.provider.d.ts +3 -4
- package/task-queue/postgres/task-queue.provider.js +2 -2
- package/task-queue/postgres/task.model.d.ts +5 -5
- package/task-queue/postgres/task.model.js +5 -5
- package/task-queue/provider.d.ts +2 -2
- package/task-queue/task-context.d.ts +34 -18
- package/task-queue/task-context.js +23 -13
- package/task-queue/task-queue.d.ts +160 -132
- package/task-queue/task-queue.js +8 -8
- package/task-queue/tests/complex.test.js +36 -29
- package/task-queue/tests/dependencies.test.js +17 -17
- package/task-queue/tests/enqueue-item.test.d.ts +1 -0
- package/task-queue/tests/enqueue-item.test.js +12 -0
- package/task-queue/tests/queue-generic.test.d.ts +1 -0
- package/task-queue/tests/queue-generic.test.js +8 -0
- package/task-queue/tests/queue.test.js +50 -50
- package/task-queue/tests/task-context.test.d.ts +1 -0
- package/task-queue/tests/task-context.test.js +7 -0
- package/task-queue/tests/task-union.test.d.ts +1 -0
- package/task-queue/tests/task-union.test.js +18 -0
- package/task-queue/tests/typing.test.d.ts +1 -0
- package/task-queue/tests/typing.test.js +9 -0
- package/task-queue/tests/worker.test.js +16 -16
- package/task-queue/types.d.ts +48 -0
- package/task-queue/types.js +1 -0
|
@@ -73,11 +73,12 @@ import { serializeError } from '../../utils/format-error.js';
|
|
|
73
73
|
import { cancelableTimeout } from '../../utils/timing.js';
|
|
74
74
|
import { isDefined, isNotNull, isNull, isString, isUndefined } from '../../utils/type-guards.js';
|
|
75
75
|
import { millisecondsPerSecond } from '../../utils/units.js';
|
|
76
|
-
import { defaultQueueConfig, DependencyJoinMode,
|
|
76
|
+
import { defaultQueueConfig, DependencyJoinMode, TaskProcessResult, TaskQueue, TaskStatus } from '../task-queue.js';
|
|
77
77
|
import { PostgresTaskQueueModuleConfig } from './module.js';
|
|
78
|
-
import { taskArchive as taskArchiveTable,
|
|
78
|
+
import { taskArchive as taskArchiveTable, taskStatus, task as taskTable } from './schemas.js';
|
|
79
79
|
import { PostgresTask, PostgresTaskArchive } from './task.model.js';
|
|
80
|
-
let
|
|
80
|
+
let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
|
|
81
|
+
#internalThis = this;
|
|
81
82
|
#repository = injectRepository(PostgresTask);
|
|
82
83
|
#archiveRepository = injectRepository(PostgresTaskArchive);
|
|
83
84
|
#config = this.config;
|
|
@@ -114,7 +115,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
114
115
|
id: RANDOM_UUID_V4,
|
|
115
116
|
namespace: this.#namespace,
|
|
116
117
|
type: sql `excluded.type`,
|
|
117
|
-
status:
|
|
118
|
+
status: TaskStatus.Pending,
|
|
118
119
|
token: null,
|
|
119
120
|
priority: sql `excluded.priority`,
|
|
120
121
|
idempotencyKey: sql `excluded.idempotency_key`,
|
|
@@ -124,7 +125,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
124
125
|
scheduleAfterTags: sql `excluded.schedule_after_tags`,
|
|
125
126
|
failFast: sql `excluded.fail_fast`,
|
|
126
127
|
dependencyJoinMode: sql `excluded.dependency_join_mode`,
|
|
127
|
-
|
|
128
|
+
dependencyTriggerStatuses: sql `excluded.dependency_trigger_statuses`,
|
|
128
129
|
tries: 0,
|
|
129
130
|
creationTimestamp: TRANSACTION_TIMESTAMP,
|
|
130
131
|
priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
|
|
@@ -160,7 +161,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
160
161
|
return {
|
|
161
162
|
namespace: this.#namespace,
|
|
162
163
|
type: item.type,
|
|
163
|
-
status: hasDependencies ?
|
|
164
|
+
status: hasDependencies ? TaskStatus.Waiting : TaskStatus.Pending,
|
|
164
165
|
token: null,
|
|
165
166
|
priority: item.priority ?? 1000,
|
|
166
167
|
idempotencyKey: item.idempotencyKey ?? null,
|
|
@@ -170,7 +171,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
170
171
|
scheduleAfterTags: item.scheduleAfterTags ?? [],
|
|
171
172
|
failFast: item.failFast ?? false,
|
|
172
173
|
dependencyJoinMode: item.dependencyJoinMode ?? DependencyJoinMode.And,
|
|
173
|
-
|
|
174
|
+
dependencyTriggerStatuses: item.dependencyTriggerStatuses ?? [TaskStatus.Completed],
|
|
174
175
|
parentId: item.parentId ?? null,
|
|
175
176
|
tries: 0,
|
|
176
177
|
progress: 0,
|
|
@@ -218,10 +219,10 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
218
219
|
}
|
|
219
220
|
await newTransaction?.commit();
|
|
220
221
|
this.#messageBus.publishAndForget();
|
|
221
|
-
const anyWaiting = tasks.some((t) => t.status ==
|
|
222
|
+
const anyWaiting = tasks.some((t) => t.status == TaskStatus.Waiting);
|
|
222
223
|
if (anyWaiting) {
|
|
223
|
-
const tagsToTrigger = distinct(tasks.filter((t) => t.status ==
|
|
224
|
-
await this.triggerTagFanIn(tagsToTrigger, transaction);
|
|
224
|
+
const tagsToTrigger = distinct(tasks.filter((t) => t.status == TaskStatus.Waiting).flatMap((t) => [...t.completeAfterTags, ...t.scheduleAfterTags]));
|
|
225
|
+
await this.triggerTagFanIn(tagsToTrigger, { transaction });
|
|
225
226
|
}
|
|
226
227
|
if (options?.returnTasks == true) {
|
|
227
228
|
return tasks;
|
|
@@ -238,46 +239,46 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
238
239
|
await result_1;
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
|
-
async has(id) {
|
|
242
|
-
return await this.#repository.hasByQuery({ namespace: this.#namespace, id });
|
|
242
|
+
async has(id, options) {
|
|
243
|
+
return await this.#repository.withOptionalTransaction(options?.transaction).hasByQuery({ namespace: this.#namespace, id });
|
|
243
244
|
}
|
|
244
|
-
async countByTags(tags) {
|
|
245
|
+
async countByTags(tags, options) {
|
|
245
246
|
const tagArray = toArray(tags);
|
|
246
247
|
if (tagArray.length == 0) {
|
|
247
248
|
return 0;
|
|
248
249
|
}
|
|
249
|
-
const [result] = await this.#repository.session
|
|
250
|
+
const [result] = await this.#repository.withOptionalTransaction(options?.transaction).session
|
|
250
251
|
.select({ count: count() })
|
|
251
252
|
.from(taskTable)
|
|
252
253
|
.where(and(eq(taskTable.namespace, this.#namespace), arrayOverlaps(taskTable.tags, tagArray)))
|
|
253
254
|
.execute();
|
|
254
255
|
return result?.count ?? 0;
|
|
255
256
|
}
|
|
256
|
-
async getTask(id) {
|
|
257
|
-
const active = await this.#repository.tryLoadByQuery({ namespace: this.#namespace, id });
|
|
257
|
+
async getTask(id, options) {
|
|
258
|
+
const active = await this.#repository.withOptionalTransaction(options?.transaction).tryLoadByQuery({ namespace: this.#namespace, id });
|
|
258
259
|
if (isDefined(active)) {
|
|
259
260
|
return active;
|
|
260
261
|
}
|
|
261
|
-
return await this.#archiveRepository.tryLoadByQuery({ namespace: this.#namespace, id });
|
|
262
|
+
return await this.#archiveRepository.withOptionalTransaction(options?.transaction).tryLoadByQuery({ namespace: this.#namespace, id });
|
|
262
263
|
}
|
|
263
|
-
async getManyByTags(tags) {
|
|
264
|
+
async getManyByTags(tags, options) {
|
|
264
265
|
const tagArray = toArray(tags);
|
|
265
266
|
if (tagArray.length == 0) {
|
|
266
267
|
return [];
|
|
267
268
|
}
|
|
268
|
-
const rows = await this.#repository.session
|
|
269
|
+
const rows = await this.#repository.withOptionalTransaction(options?.transaction).session
|
|
269
270
|
.select()
|
|
270
271
|
.from(taskTable)
|
|
271
272
|
.where(and(eq(taskTable.namespace, this.#namespace), arrayOverlaps(taskTable.tags, tagArray)))
|
|
272
273
|
.execute();
|
|
273
|
-
return await this.#repository.mapManyToEntity(rows);
|
|
274
|
+
return await this.#repository.withOptionalTransaction(options?.transaction).mapManyToEntity(rows);
|
|
274
275
|
}
|
|
275
|
-
async getTree(
|
|
276
|
-
const ids = toArray(
|
|
277
|
-
return await this.getTreeByQuery({ id: { $in: ids } },
|
|
276
|
+
async getTree(rootId, options) {
|
|
277
|
+
const ids = toArray(rootId);
|
|
278
|
+
return await this.getTreeByQuery({ id: { $in: ids } }, options);
|
|
278
279
|
}
|
|
279
|
-
async getTreeByQuery(query,
|
|
280
|
-
return await this.#repository.useTransaction(transaction, async (tx) => {
|
|
280
|
+
async getTreeByQuery(query, options) {
|
|
281
|
+
return await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
281
282
|
const repositoryWithTransaction = this.#repository.withTransaction(tx);
|
|
282
283
|
const roots = await repositoryWithTransaction.loadManyByQuery(query);
|
|
283
284
|
if (roots.length == 0) {
|
|
@@ -299,38 +300,38 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
299
300
|
)
|
|
300
301
|
SELECT * FROM task_tree
|
|
301
302
|
`);
|
|
302
|
-
const rows = rawResult.rows
|
|
303
|
+
const rows = rawResult.rows;
|
|
303
304
|
return await repositoryWithTransaction.mapManyToEntity(rows);
|
|
304
305
|
});
|
|
305
306
|
}
|
|
306
|
-
async cancel(id,
|
|
307
|
-
await this.cancelMany([id],
|
|
307
|
+
async cancel(id, options) {
|
|
308
|
+
await this.cancelMany([id], options);
|
|
308
309
|
}
|
|
309
|
-
async cancelMany(ids,
|
|
310
|
-
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
311
|
-
const tree = await this.getTree(ids, tx);
|
|
310
|
+
async cancelMany(ids, options) {
|
|
311
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
312
|
+
const tree = await this.getTree(ids, { transaction: tx });
|
|
312
313
|
const treeIds = tree.map((task) => task.id);
|
|
313
314
|
if (treeIds.length == 0) {
|
|
314
315
|
return;
|
|
315
316
|
}
|
|
316
317
|
await this.#repository.withTransaction(tx).updateMany(treeIds, {
|
|
317
|
-
status:
|
|
318
|
+
status: TaskStatus.Cancelled,
|
|
318
319
|
token: null,
|
|
319
320
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
320
321
|
});
|
|
321
322
|
const tags = tree.flatMap((t) => t.tags);
|
|
322
|
-
await this.triggerTagFanIn(tags, tx);
|
|
323
|
+
await this.triggerTagFanIn(tags, { transaction: tx });
|
|
323
324
|
});
|
|
324
325
|
}
|
|
325
|
-
async cancelManyByTags(tags) {
|
|
326
|
-
await this.#repository.transaction
|
|
327
|
-
const tasks = await this.getManyByTags(tags);
|
|
326
|
+
async cancelManyByTags(tags, options) {
|
|
327
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
328
|
+
const tasks = await this.getManyByTags(tags, { transaction: tx });
|
|
328
329
|
const ids = tasks.map((t) => t.id);
|
|
329
|
-
await this.cancelMany(ids, tx);
|
|
330
|
+
await this.cancelMany(ids, { transaction: tx });
|
|
330
331
|
});
|
|
331
332
|
}
|
|
332
|
-
async clear() {
|
|
333
|
-
await this.#repository.transaction
|
|
333
|
+
async clear(options) {
|
|
334
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
334
335
|
const repository = this.#repository.withTransaction(tx);
|
|
335
336
|
const parentIds = repository.session
|
|
336
337
|
.select({ id: taskTable.id })
|
|
@@ -365,12 +366,12 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
365
366
|
return [];
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
|
-
const tasks = await this.#repository.transaction
|
|
369
|
+
const tasks = await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
369
370
|
const repository = this.#repository.withTransaction(tx);
|
|
370
371
|
let effectiveCount = count;
|
|
371
372
|
if (!forceDequeue && circuitBreakerResult?.state == CircuitBreakerState.HalfOpen) {
|
|
372
373
|
if (circuitBreakerResult.isProbe != true) {
|
|
373
|
-
const runningCount = await repository.countByQuery({ namespace: this.#namespace, status:
|
|
374
|
+
const runningCount = await repository.countByQuery({ namespace: this.#namespace, status: TaskStatus.Running });
|
|
374
375
|
if (runningCount > 0) {
|
|
375
376
|
return [];
|
|
376
377
|
}
|
|
@@ -381,7 +382,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
381
382
|
if (!forceDequeue && isNotNull(this.globalConcurrency)) {
|
|
382
383
|
// WARN: This is a check-then-act race condition.
|
|
383
384
|
// A distributed lock or an atomic update strategy is needed for strict enforcement.
|
|
384
|
-
const runningCount = await repository.countByQuery({ namespace: this.#namespace, status:
|
|
385
|
+
const runningCount = await repository.countByQuery({ namespace: this.#namespace, status: TaskStatus.Running });
|
|
385
386
|
if (runningCount >= this.globalConcurrency) {
|
|
386
387
|
return [];
|
|
387
388
|
}
|
|
@@ -395,7 +396,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
395
396
|
const selection = repository.session.$with('selection').as((qb) => qb
|
|
396
397
|
.select({ id: taskTable.id })
|
|
397
398
|
.from(taskTable)
|
|
398
|
-
.where(and(eq(taskTable.namespace, this.#namespace), lte(taskTable.scheduleTimestamp, TRANSACTION_TIMESTAMP), eq(taskTable.status,
|
|
399
|
+
.where(and(eq(taskTable.namespace, this.#namespace), lte(taskTable.scheduleTimestamp, TRANSACTION_TIMESTAMP), eq(taskTable.status, TaskStatus.Pending), or(sqlIsNull(taskTable.timeToLive), lt(TRANSACTION_TIMESTAMP, taskTable.timeToLive)), isDefined(options?.types) ? inArray(taskTable.type, options.types) : undefined, sql `pg_sleep(0) IS NOT NULL` // Materialization hack until drizzle implements https://github.com/drizzle-team/drizzle-orm/issues/2318
|
|
399
400
|
))
|
|
400
401
|
.orderBy(asc(taskTable.priority), asc(taskTable.scheduleTimestamp))
|
|
401
402
|
.limit(effectiveCount)
|
|
@@ -404,7 +405,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
404
405
|
.with(selection)
|
|
405
406
|
.update(taskTable)
|
|
406
407
|
.set({
|
|
407
|
-
status:
|
|
408
|
+
status: TaskStatus.Running,
|
|
408
409
|
token: RANDOM_UUID_V4,
|
|
409
410
|
visibilityDeadline: sql `${TRANSACTION_TIMESTAMP} + ${interval(this.visibilityTimeout, 'milliseconds')}`,
|
|
410
411
|
startTimestamp: TRANSACTION_TIMESTAMP,
|
|
@@ -423,29 +424,29 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
423
424
|
}
|
|
424
425
|
return tasks;
|
|
425
426
|
}
|
|
426
|
-
async reschedule(id, timestamp,
|
|
427
|
-
await this.rescheduleMany([id], timestamp,
|
|
427
|
+
async reschedule(id, timestamp, options) {
|
|
428
|
+
await this.rescheduleMany([id], timestamp, options);
|
|
428
429
|
}
|
|
429
|
-
async rescheduleMany(ids, timestamp,
|
|
430
|
-
await this.#repository.withOptionalTransaction(transaction).updateMany(ids, {
|
|
431
|
-
status:
|
|
430
|
+
async rescheduleMany(ids, timestamp, options) {
|
|
431
|
+
await this.#repository.withOptionalTransaction(options?.transaction).updateMany(ids, {
|
|
432
|
+
status: TaskStatus.Pending,
|
|
432
433
|
token: null,
|
|
433
434
|
scheduleTimestamp: timestamp,
|
|
434
435
|
visibilityDeadline: null,
|
|
435
436
|
tries: sql `CASE
|
|
436
|
-
WHEN ${taskTable.status} = ${
|
|
437
|
+
WHEN ${taskTable.status} = ${TaskStatus.Running} THEN GREATEST(0, ${taskTable.tries} - 1)
|
|
437
438
|
ELSE ${taskTable.tries}
|
|
438
439
|
END`,
|
|
439
440
|
});
|
|
440
441
|
}
|
|
441
|
-
async rescheduleManyByTags(tags, timestamp,
|
|
442
|
-
await this.#repository.withOptionalTransaction(transaction).updateManyByQuery(and(eq(taskTable.namespace, this.#namespace), arrayOverlaps(taskTable.tags, toArray(tags))), {
|
|
443
|
-
status:
|
|
442
|
+
async rescheduleManyByTags(tags, timestamp, options) {
|
|
443
|
+
await this.#repository.withOptionalTransaction(options?.transaction).updateManyByQuery(and(eq(taskTable.namespace, this.#namespace), arrayOverlaps(taskTable.tags, toArray(tags))), {
|
|
444
|
+
status: TaskStatus.Pending,
|
|
444
445
|
token: null,
|
|
445
446
|
scheduleTimestamp: timestamp,
|
|
446
447
|
visibilityDeadline: null,
|
|
447
448
|
tries: sql `CASE
|
|
448
|
-
WHEN ${taskTable.status} = ${
|
|
449
|
+
WHEN ${taskTable.status} = ${TaskStatus.Running} THEN GREATEST(0, ${taskTable.tries} - 1)
|
|
449
450
|
ELSE ${taskTable.tries}
|
|
450
451
|
END`,
|
|
451
452
|
});
|
|
@@ -468,28 +469,28 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
468
469
|
const result = await repository.tryUpdateByQuery({
|
|
469
470
|
namespace: this.#namespace,
|
|
470
471
|
id: task.id,
|
|
471
|
-
status:
|
|
472
|
+
status: TaskStatus.Running,
|
|
472
473
|
token: task.token,
|
|
473
474
|
startTimestamp: { $gt: sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}` },
|
|
474
475
|
}, update);
|
|
475
476
|
// TODO: reduce required DB roundtrips
|
|
476
477
|
if (isUndefined(result)) {
|
|
477
|
-
const existing = await repository.tryLoadByQuery({ id: task.id, namespace: this.#namespace, status:
|
|
478
|
+
const existing = await repository.tryLoadByQuery({ id: task.id, namespace: this.#namespace, status: TaskStatus.Running, token: task.token });
|
|
478
479
|
if (isDefined(existing) && isNotNull(existing.startTimestamp) && (currentTimestamp() - existing.startTimestamp) > this.maxExecutionTime) {
|
|
479
|
-
await this.fail(task, { message: 'Hard Execution Timeout' }, true, tx);
|
|
480
|
+
await this.fail(task, { message: 'Hard Execution Timeout' }, { fatal: true, transaction: tx });
|
|
480
481
|
}
|
|
481
482
|
}
|
|
482
483
|
return result;
|
|
483
484
|
});
|
|
484
485
|
}
|
|
485
|
-
async touchMany(tasks,
|
|
486
|
+
async touchMany(tasks, options) {
|
|
486
487
|
if (tasks.length == 0) {
|
|
487
488
|
return [];
|
|
488
489
|
}
|
|
489
|
-
const repository = this.#repository.withOptionalTransaction(transaction);
|
|
490
|
+
const repository = this.#repository.withOptionalTransaction(options?.transaction);
|
|
490
491
|
const rows = tasks.map((t, i) => {
|
|
491
|
-
const progress = progresses?.[i] ?? null;
|
|
492
|
-
const state = states?.[i] ?? null;
|
|
492
|
+
const progress = options?.progresses?.[i] ?? null;
|
|
493
|
+
const state = options?.states?.[i] ?? null;
|
|
493
494
|
return sql `(${t.id}::uuid, ${t.token}::uuid, ${progress}::numeric, ${state}::jsonb)`;
|
|
494
495
|
});
|
|
495
496
|
const updates = repository.session.$with('updates').as((qb) => qb
|
|
@@ -508,7 +509,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
508
509
|
state: coalesce(updates.updateState, taskTable.state),
|
|
509
510
|
})
|
|
510
511
|
.from(updates)
|
|
511
|
-
.where(and(eq(taskTable.id, updates.updateId), eq(taskTable.namespace, this.#namespace), eq(taskTable.token, updates.updateToken), eq(taskTable.status,
|
|
512
|
+
.where(and(eq(taskTable.id, updates.updateId), eq(taskTable.namespace, this.#namespace), eq(taskTable.token, updates.updateToken), eq(taskTable.status, TaskStatus.Running), gt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`)))
|
|
512
513
|
.returning({ id: taskTable.id }));
|
|
513
514
|
const result = await repository.session
|
|
514
515
|
.with(updates, updated)
|
|
@@ -517,16 +518,16 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
517
518
|
.execute();
|
|
518
519
|
return result.map((r) => r.id);
|
|
519
520
|
}
|
|
520
|
-
async complete(task,
|
|
521
|
-
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
521
|
+
async complete(task, options) {
|
|
522
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
522
523
|
const repository = this.#repository.withTransaction(tx);
|
|
523
524
|
const updatedTask = await repository.tryUpdateByQuery({
|
|
524
525
|
id: task.id,
|
|
525
526
|
token: task.token,
|
|
526
527
|
}, {
|
|
527
|
-
status:
|
|
528
|
+
status: TaskStatus.Completed,
|
|
528
529
|
token: null,
|
|
529
|
-
result: result,
|
|
530
|
+
result: options?.result,
|
|
530
531
|
progress: 1,
|
|
531
532
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
532
533
|
visibilityDeadline: null,
|
|
@@ -535,17 +536,17 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
535
536
|
return;
|
|
536
537
|
}
|
|
537
538
|
await this.#circuitBreaker.recordSuccess();
|
|
538
|
-
await this.triggerTagFanIn(task.tags, tx);
|
|
539
|
+
await this.triggerTagFanIn(task.tags, { transaction: tx });
|
|
539
540
|
});
|
|
540
541
|
}
|
|
541
|
-
async completeMany(tasks,
|
|
542
|
+
async completeMany(tasks, options) {
|
|
542
543
|
if (tasks.length == 0) {
|
|
543
544
|
return;
|
|
544
545
|
}
|
|
545
|
-
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
546
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
546
547
|
const repository = this.#repository.withTransaction(tx);
|
|
547
548
|
const rows = tasks.map((t, i) => {
|
|
548
|
-
const result =
|
|
549
|
+
const result = options?.results?.[i] ?? null;
|
|
549
550
|
return sql `(${t.id}::uuid, ${t.token}::uuid, ${result}::jsonb)`;
|
|
550
551
|
});
|
|
551
552
|
const updates = repository.session.$with('updates').as((qb) => qb
|
|
@@ -558,7 +559,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
558
559
|
const updated = repository.session.$with('updated').as(() => repository.session
|
|
559
560
|
.update(taskTable)
|
|
560
561
|
.set({
|
|
561
|
-
status:
|
|
562
|
+
status: TaskStatus.Completed,
|
|
562
563
|
token: null,
|
|
563
564
|
result: updates.updateResult,
|
|
564
565
|
progress: 1,
|
|
@@ -576,18 +577,18 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
576
577
|
if (updatedRows.length > 0) {
|
|
577
578
|
await this.#circuitBreaker.recordSuccess();
|
|
578
579
|
const tags = updatedRows.flatMap((t) => t.tags);
|
|
579
|
-
await this.triggerTagFanIn(tags, tx);
|
|
580
|
+
await this.triggerTagFanIn(tags, { transaction: tx });
|
|
580
581
|
}
|
|
581
582
|
});
|
|
582
583
|
}
|
|
583
|
-
async fail(task, error,
|
|
584
|
-
const isRetryable =
|
|
585
|
-
const nextStatus = isRetryable ?
|
|
584
|
+
async fail(task, error, options) {
|
|
585
|
+
const isRetryable = (options?.fatal != true) && (task.tries < this.maxTries);
|
|
586
|
+
const nextStatus = isRetryable ? TaskStatus.Pending : TaskStatus.Dead;
|
|
586
587
|
const delay = isRetryable
|
|
587
588
|
? Math.min(this.retryDelayMaximum, this.retryDelayMinimum * (this.retryDelayGrowth ** task.tries))
|
|
588
589
|
: 0;
|
|
589
590
|
const nextSchedule = currentTimestamp() + delay;
|
|
590
|
-
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
591
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
591
592
|
const updatedTask = await this.#repository.withTransaction(tx).tryUpdateByQuery({
|
|
592
593
|
namespace: this.#namespace,
|
|
593
594
|
id: task.id,
|
|
@@ -600,46 +601,46 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
600
601
|
visibilityDeadline: null,
|
|
601
602
|
scheduleTimestamp: nextSchedule,
|
|
602
603
|
startTimestamp: null,
|
|
603
|
-
completeTimestamp: (nextStatus ==
|
|
604
|
+
completeTimestamp: (nextStatus == TaskStatus.Dead) ? TRANSACTION_TIMESTAMP : null,
|
|
604
605
|
});
|
|
605
606
|
if (isUndefined(updatedTask)) {
|
|
606
607
|
return;
|
|
607
608
|
}
|
|
608
609
|
await this.#circuitBreaker.recordFailure();
|
|
609
|
-
if (nextStatus ==
|
|
610
|
-
await this.triggerTagFanIn(task.tags, tx);
|
|
610
|
+
if (nextStatus == TaskStatus.Dead) {
|
|
611
|
+
await this.triggerTagFanIn(task.tags, { transaction: tx });
|
|
611
612
|
}
|
|
612
613
|
});
|
|
613
614
|
}
|
|
614
|
-
async triggerTagFanIn(tags,
|
|
615
|
+
async triggerTagFanIn(tags, options) {
|
|
615
616
|
if (tags.length == 0) {
|
|
616
617
|
return;
|
|
617
618
|
}
|
|
618
619
|
const distinctSortedTags = distinct(tags).toSorted();
|
|
619
620
|
const hash = await digest('SHA-256', distinctSortedTags.join(',')).toHex();
|
|
620
621
|
const idempotencyKey = `Sys:FanIn:${hash}`;
|
|
621
|
-
await this.enqueue('[SystemWorker]:FanIn', { targetTags: distinctSortedTags }, {
|
|
622
|
+
await this.#internalThis.enqueue('[SystemWorker]:FanIn', { targetTags: distinctSortedTags }, {
|
|
622
623
|
priority: 0,
|
|
623
624
|
idempotencyKey,
|
|
624
625
|
replace: true,
|
|
625
|
-
transaction,
|
|
626
|
+
transaction: options?.transaction,
|
|
626
627
|
});
|
|
627
628
|
}
|
|
628
|
-
async failMany(tasks, errors,
|
|
629
|
+
async failMany(tasks, errors, options) {
|
|
629
630
|
if (tasks.length == 0) {
|
|
630
631
|
return;
|
|
631
632
|
}
|
|
632
|
-
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
633
|
+
await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
633
634
|
const repository = this.#repository.withTransaction(tx);
|
|
634
635
|
const rows = tasks.map((task, index) => {
|
|
635
636
|
const error = errors[index];
|
|
636
637
|
const isRetryable = (task.tries < this.maxTries);
|
|
637
|
-
const nextStatus = isRetryable ?
|
|
638
|
+
const nextStatus = isRetryable ? TaskStatus.Pending : TaskStatus.Dead;
|
|
638
639
|
const delay = isRetryable
|
|
639
640
|
? Math.min(this.retryDelayMaximum, this.retryDelayMinimum * (this.retryDelayGrowth ** task.tries))
|
|
640
641
|
: 0;
|
|
641
642
|
const nextSchedule = new Date(currentTimestamp() + delay);
|
|
642
|
-
const completeTimestamp = (nextStatus ==
|
|
643
|
+
const completeTimestamp = (nextStatus == TaskStatus.Dead) ? new Date() : null;
|
|
643
644
|
return sql `(${task.id}::uuid, ${task.token}::uuid, ${task.tries}::int, ${nextStatus}::text, ${serializeError(error)}::jsonb, ${nextSchedule}::timestamptz, ${completeTimestamp}::timestamptz)`;
|
|
644
645
|
});
|
|
645
646
|
const updates = repository.session.$with('updates').as((qb) => qb
|
|
@@ -656,7 +657,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
656
657
|
const updated = repository.session.$with('updated').as(() => repository.session
|
|
657
658
|
.update(taskTable)
|
|
658
659
|
.set({
|
|
659
|
-
status: sql `${updates.updateStatus}::${
|
|
660
|
+
status: sql `${updates.updateStatus}::${taskStatus}`,
|
|
660
661
|
token: null,
|
|
661
662
|
error: sql `${updates.updateError}`,
|
|
662
663
|
visibilityDeadline: null,
|
|
@@ -675,9 +676,9 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
675
676
|
if (result.length > 0) {
|
|
676
677
|
await Promise.all(createArray(result.length, async () => await this.#circuitBreaker.recordFailure()));
|
|
677
678
|
const deadTags = result
|
|
678
|
-
.filter((r) => r.status ==
|
|
679
|
+
.filter((r) => r.status == TaskStatus.Dead)
|
|
679
680
|
.flatMap((r) => r.tags);
|
|
680
|
-
await this.triggerTagFanIn(deadTags, tx);
|
|
681
|
+
await this.triggerTagFanIn(deadTags, { transaction: tx });
|
|
681
682
|
}
|
|
682
683
|
});
|
|
683
684
|
}
|
|
@@ -688,7 +689,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
688
689
|
const waiters = await repository.session
|
|
689
690
|
.select()
|
|
690
691
|
.from(taskTable)
|
|
691
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
692
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Waiting), or(arrayOverlaps(taskTable.completeAfterTags, targetTags), arrayOverlaps(taskTable.scheduleAfterTags, targetTags))))
|
|
692
693
|
.for('update', { skipLocked: true })
|
|
693
694
|
.execute();
|
|
694
695
|
const entities = await repository.mapManyToEntity(waiters);
|
|
@@ -712,13 +713,13 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
712
713
|
}
|
|
713
714
|
tagStats.get(row.tag).set(row.status, row.count);
|
|
714
715
|
}
|
|
715
|
-
const getCount = (tags,
|
|
716
|
+
const getCount = (tags, Statuses) => {
|
|
716
717
|
let sum = 0;
|
|
717
718
|
for (const tag of tags) {
|
|
718
719
|
const stats = tagStats.get(tag);
|
|
719
720
|
if (isDefined(stats)) {
|
|
720
|
-
for (const
|
|
721
|
-
sum += stats.get(
|
|
721
|
+
for (const status of Statuses) {
|
|
722
|
+
sum += stats.get(status) ?? 0;
|
|
722
723
|
}
|
|
723
724
|
}
|
|
724
725
|
}
|
|
@@ -734,23 +735,28 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
734
735
|
let shouldFail = false;
|
|
735
736
|
// 1. Check Fail Fast
|
|
736
737
|
if (waiter.failFast) {
|
|
737
|
-
if (getCount(requiredTags, [
|
|
738
|
+
if (getCount(requiredTags, [TaskStatus.Dead, TaskStatus.Cancelled]) > 0) {
|
|
738
739
|
shouldFail = true;
|
|
739
740
|
}
|
|
740
741
|
}
|
|
741
742
|
// 2. Check Trigger Conditions
|
|
742
743
|
if (!shouldFail) {
|
|
743
744
|
if (waiter.dependencyJoinMode == DependencyJoinMode.Or) {
|
|
744
|
-
if (getCount(requiredTags, waiter.
|
|
745
|
+
if (getCount(requiredTags, waiter.dependencyTriggerStatuses) > 0) {
|
|
745
746
|
isReady = true;
|
|
746
747
|
}
|
|
747
748
|
}
|
|
748
749
|
else if (waiter.dependencyJoinMode == DependencyJoinMode.And) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
750
|
+
isReady = requiredTags.every((tag) => {
|
|
751
|
+
const stats = tagStats.get(tag);
|
|
752
|
+
if (isUndefined(stats)) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
const activeCount = (stats.get(TaskStatus.Pending) ?? 0)
|
|
756
|
+
+ (stats.get(TaskStatus.Running) ?? 0)
|
|
757
|
+
+ (stats.get(TaskStatus.Waiting) ?? 0);
|
|
758
|
+
return activeCount == 0;
|
|
759
|
+
});
|
|
754
760
|
}
|
|
755
761
|
}
|
|
756
762
|
// 3. Transition
|
|
@@ -760,8 +766,9 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
760
766
|
else if (isReady) {
|
|
761
767
|
if (waiter.scheduleAfterTags.length > 0) {
|
|
762
768
|
idsToSchedule.push(waiter.id);
|
|
769
|
+
fanInTags.push(...waiter.tags);
|
|
763
770
|
}
|
|
764
|
-
else {
|
|
771
|
+
else if (waiter.completeAfterTags.length > 0) {
|
|
765
772
|
idsToComplete.push(waiter.id);
|
|
766
773
|
fanInTags.push(...waiter.tags);
|
|
767
774
|
}
|
|
@@ -769,43 +776,43 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
769
776
|
}
|
|
770
777
|
if (idsToFail.length > 0) {
|
|
771
778
|
const rows = await repository.updateMany(idsToFail, {
|
|
772
|
-
status:
|
|
779
|
+
status: TaskStatus.Dead,
|
|
773
780
|
error: { code: 'DependencyFailed' },
|
|
774
781
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
775
782
|
});
|
|
776
783
|
const tags = rows.flatMap((r) => r.tags);
|
|
777
|
-
await this.triggerTagFanIn(tags, tx);
|
|
784
|
+
await this.triggerTagFanIn(tags, { transaction: tx });
|
|
778
785
|
}
|
|
779
786
|
if (idsToSchedule.length > 0) {
|
|
780
787
|
await repository.updateMany(idsToSchedule, {
|
|
781
|
-
status:
|
|
788
|
+
status: TaskStatus.Pending,
|
|
782
789
|
scheduleTimestamp: TRANSACTION_TIMESTAMP,
|
|
783
790
|
});
|
|
784
791
|
}
|
|
785
792
|
if (idsToComplete.length > 0) {
|
|
786
793
|
await repository.updateMany(idsToComplete, {
|
|
787
|
-
status:
|
|
794
|
+
status: TaskStatus.Completed,
|
|
788
795
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
789
796
|
progress: 1,
|
|
790
797
|
token: null,
|
|
791
798
|
});
|
|
792
|
-
await this.triggerTagFanIn(fanInTags, tx);
|
|
799
|
+
await this.triggerTagFanIn(fanInTags, { transaction: tx });
|
|
793
800
|
}
|
|
794
801
|
});
|
|
795
802
|
}
|
|
796
|
-
async maintenance() {
|
|
803
|
+
async maintenance(options) {
|
|
797
804
|
const repository = this.#repository;
|
|
798
805
|
const archiveRepository = this.#archiveRepository;
|
|
799
806
|
// 1. Archival: Move old terminal tasks to archive
|
|
800
807
|
while (true) {
|
|
801
|
-
const archivedCount = await repository.transaction
|
|
808
|
+
const archivedCount = await repository.useTransaction(options?.transaction, async (tx) => {
|
|
802
809
|
const repositoryWithTx = repository.withTransaction(tx);
|
|
803
810
|
const archiveRepositoryWithTx = archiveRepository.withTransaction(tx);
|
|
804
811
|
const tasksToArchive = await repositoryWithTx.loadManyByQuery({
|
|
805
812
|
$and: [
|
|
806
813
|
{
|
|
807
814
|
namespace: this.#namespace,
|
|
808
|
-
status: { $in: [
|
|
815
|
+
status: { $in: [TaskStatus.Completed, TaskStatus.Dead, TaskStatus.Cancelled] },
|
|
809
816
|
completeTimestamp: { $lt: sql `${TRANSACTION_TIMESTAMP} - ${interval(this.retention, 'milliseconds')}` },
|
|
810
817
|
},
|
|
811
818
|
notInArray(taskTable.id, repositoryWithTx.session
|
|
@@ -826,7 +833,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
826
833
|
}
|
|
827
834
|
// 2. Purge Archive: Remove very old archived tasks
|
|
828
835
|
while (true) {
|
|
829
|
-
const deletedArchiveCount = await archiveRepository.transaction
|
|
836
|
+
const deletedArchiveCount = await archiveRepository.useTransaction(options?.transaction, async (tx) => {
|
|
830
837
|
const repositoryWithTx = archiveRepository.withTransaction(tx);
|
|
831
838
|
const selection = repositoryWithTx.session.$with('archive_purge_selection').as((qb) => qb
|
|
832
839
|
.select({ id: taskArchiveTable.id })
|
|
@@ -846,21 +853,21 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
846
853
|
}
|
|
847
854
|
// 3. Maintenance Loop
|
|
848
855
|
while (true) {
|
|
849
|
-
const maintenanceCount = await repository.transaction
|
|
856
|
+
const maintenanceCount = await repository.useTransaction(options?.transaction, async (tx) => {
|
|
850
857
|
const repositoryWithTx = repository.withTransaction(tx);
|
|
851
858
|
let totalUpdated = 0;
|
|
852
859
|
// 3.1 Handle Pending Expiration
|
|
853
860
|
const expiredSelection = repositoryWithTx.session.$with('expired_selection').as((qb) => qb
|
|
854
861
|
.select({ id: taskTable.id })
|
|
855
862
|
.from(taskTable)
|
|
856
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
863
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Pending), lt(taskTable.timeToLive, TRANSACTION_TIMESTAMP)))
|
|
857
864
|
.limit(1000)
|
|
858
865
|
.for('update', { skipLocked: true }));
|
|
859
866
|
const expiredRows = await repositoryWithTx.session
|
|
860
867
|
.with(expiredSelection)
|
|
861
868
|
.update(taskTable)
|
|
862
869
|
.set({
|
|
863
|
-
status:
|
|
870
|
+
status: TaskStatus.Dead,
|
|
864
871
|
token: null,
|
|
865
872
|
error: { code: 'Expired', message: 'Task expired before processing' },
|
|
866
873
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
@@ -869,20 +876,20 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
869
876
|
.returning({ tags: taskTable.tags });
|
|
870
877
|
totalUpdated += expiredRows.length;
|
|
871
878
|
if (expiredRows.length > 0) {
|
|
872
|
-
await this.triggerTagFanIn(expiredRows.flatMap((r) => r.tags), tx);
|
|
879
|
+
await this.triggerTagFanIn(expiredRows.flatMap((r) => r.tags), { transaction: tx });
|
|
873
880
|
}
|
|
874
881
|
// 3.2 Handle Zombie Tasks (Retry)
|
|
875
882
|
const zombieRetrySelection = repositoryWithTx.session.$with('zombie_retry_selection').as((qb) => qb
|
|
876
883
|
.select({ id: taskTable.id })
|
|
877
884
|
.from(taskTable)
|
|
878
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
885
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Running), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP), lt(taskTable.tries, this.maxTries)))
|
|
879
886
|
.limit(1000)
|
|
880
887
|
.for('update', { skipLocked: true }));
|
|
881
888
|
const zombieRetryRows = await repositoryWithTx.session
|
|
882
889
|
.with(zombieRetrySelection)
|
|
883
890
|
.update(taskTable)
|
|
884
891
|
.set({
|
|
885
|
-
status:
|
|
892
|
+
status: TaskStatus.Pending,
|
|
886
893
|
token: null,
|
|
887
894
|
visibilityDeadline: null,
|
|
888
895
|
scheduleTimestamp: sql `${TRANSACTION_TIMESTAMP} + ${interval(this.retryDelayMinimum, 'milliseconds')}`, // Simple backoff for zombies
|
|
@@ -895,14 +902,14 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
895
902
|
const zombieExhaustionSelection = repositoryWithTx.session.$with('zombie_exhaustion_selection').as((qb) => qb
|
|
896
903
|
.select({ id: taskTable.id })
|
|
897
904
|
.from(taskTable)
|
|
898
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
905
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Running), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP), gte(taskTable.tries, this.maxTries)))
|
|
899
906
|
.limit(1000)
|
|
900
907
|
.for('update', { skipLocked: true }));
|
|
901
908
|
const exhaustionRows = await repositoryWithTx.session
|
|
902
909
|
.with(zombieExhaustionSelection)
|
|
903
910
|
.update(taskTable)
|
|
904
911
|
.set({
|
|
905
|
-
status:
|
|
912
|
+
status: TaskStatus.Dead,
|
|
906
913
|
token: null,
|
|
907
914
|
visibilityDeadline: null,
|
|
908
915
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
@@ -912,20 +919,20 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
912
919
|
.returning({ tags: taskTable.tags });
|
|
913
920
|
totalUpdated += exhaustionRows.length;
|
|
914
921
|
if (exhaustionRows.length > 0) {
|
|
915
|
-
await this.triggerTagFanIn(exhaustionRows.flatMap((r) => r.tags), tx);
|
|
922
|
+
await this.triggerTagFanIn(exhaustionRows.flatMap((r) => r.tags), { transaction: tx });
|
|
916
923
|
}
|
|
917
924
|
// 3.4 Handle Hard Timeout
|
|
918
925
|
const timeoutSelection = repositoryWithTx.session.$with('timeout_selection').as((qb) => qb
|
|
919
926
|
.select({ id: taskTable.id })
|
|
920
927
|
.from(taskTable)
|
|
921
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
928
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Running), lt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.maxExecutionTime, 'milliseconds')}`)))
|
|
922
929
|
.limit(1000)
|
|
923
930
|
.for('update', { skipLocked: true }));
|
|
924
931
|
const timeoutRows = await repositoryWithTx.session
|
|
925
932
|
.with(timeoutSelection)
|
|
926
933
|
.update(taskTable)
|
|
927
934
|
.set({
|
|
928
|
-
status:
|
|
935
|
+
status: TaskStatus.Dead,
|
|
929
936
|
token: null,
|
|
930
937
|
visibilityDeadline: null,
|
|
931
938
|
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
@@ -935,13 +942,13 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
935
942
|
.returning({ tags: taskTable.tags });
|
|
936
943
|
totalUpdated += timeoutRows.length;
|
|
937
944
|
if (timeoutRows.length > 0) {
|
|
938
|
-
await this.triggerTagFanIn(timeoutRows.flatMap((r) => r.tags), tx);
|
|
945
|
+
await this.triggerTagFanIn(timeoutRows.flatMap((r) => r.tags), { transaction: tx });
|
|
939
946
|
}
|
|
940
947
|
// 3.5 Promote Priority (Aging)
|
|
941
948
|
const agingSelection = repositoryWithTx.session.$with('aging_selection').as((qb) => qb
|
|
942
949
|
.select({ id: taskTable.id })
|
|
943
950
|
.from(taskTable)
|
|
944
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status,
|
|
951
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.status, TaskStatus.Pending), lt(taskTable.priorityAgeTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.priorityAgingInterval, 'milliseconds')}`)))
|
|
945
952
|
.limit(1000)
|
|
946
953
|
.for('update', { skipLocked: true }));
|
|
947
954
|
const agingRows = await repositoryWithTx.session
|
|
@@ -966,7 +973,7 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
966
973
|
await repository.session
|
|
967
974
|
.update(taskTable)
|
|
968
975
|
.set({
|
|
969
|
-
status:
|
|
976
|
+
status: TaskStatus.Pending,
|
|
970
977
|
token: null,
|
|
971
978
|
error: null,
|
|
972
979
|
result: null,
|
|
@@ -975,9 +982,9 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
975
982
|
tries: 0,
|
|
976
983
|
progress: 0,
|
|
977
984
|
priorityAgeTimestamp: TRANSACTION_TIMESTAMP,
|
|
978
|
-
state: options?.resetState == true ? null : undefined,
|
|
985
|
+
state: (options?.resetState == true) ? null : undefined,
|
|
979
986
|
})
|
|
980
|
-
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.id, id), or(eq(taskTable.status,
|
|
987
|
+
.where(and(eq(taskTable.namespace, this.#namespace), eq(taskTable.id, id), or(eq(taskTable.status, TaskStatus.Pending), eq(taskTable.status, TaskStatus.Completed), eq(taskTable.status, TaskStatus.Cancelled), eq(taskTable.status, TaskStatus.Dead), lt(taskTable.visibilityDeadline, TRANSACTION_TIMESTAMP))))
|
|
981
988
|
.execute();
|
|
982
989
|
}
|
|
983
990
|
async *getConsumer(cancellationSignal, options) {
|
|
@@ -1004,31 +1011,34 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
1004
1011
|
}
|
|
1005
1012
|
}
|
|
1006
1013
|
}
|
|
1007
|
-
|
|
1014
|
+
/**
|
|
1015
|
+
* @internal for internal unit test use only
|
|
1016
|
+
* @param options
|
|
1017
|
+
* @returns
|
|
1018
|
+
*/
|
|
1019
|
+
async processPendingFanIn(options) {
|
|
1008
1020
|
let processedCount = 0;
|
|
1009
1021
|
while (true) {
|
|
1010
|
-
const task = await this.dequeue({ types: ['[SystemWorker]:FanIn'], forceDequeue: true });
|
|
1022
|
+
const task = await this.#internalThis.dequeue({ types: ['[SystemWorker]:FanIn'], forceDequeue: true, transaction: options?.transaction });
|
|
1011
1023
|
if (isUndefined(task)) {
|
|
1012
1024
|
break;
|
|
1013
1025
|
}
|
|
1014
1026
|
try {
|
|
1015
|
-
|
|
1016
|
-
await this.
|
|
1017
|
-
await this.complete(task, undefined, transaction);
|
|
1027
|
+
await this.resolveDependencies(task.data.targetTags);
|
|
1028
|
+
await this.#internalThis.complete(task, { transaction: options?.transaction });
|
|
1018
1029
|
processedCount++;
|
|
1019
1030
|
}
|
|
1020
1031
|
catch (error) {
|
|
1021
|
-
await this.fail(task, error,
|
|
1032
|
+
await this.#internalThis.fail(task, error, { transaction: options?.transaction });
|
|
1022
1033
|
throw error;
|
|
1023
1034
|
}
|
|
1024
1035
|
}
|
|
1025
1036
|
return processedCount;
|
|
1026
1037
|
}
|
|
1027
1038
|
startSystemWorker() {
|
|
1028
|
-
this.process({ concurrency: 1, cancellationSignal: this.#cancellationSignal, forceDequeue: true, types: ['[SystemWorker]:FanIn'] }, async (context) => {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
return TaskResult.Complete();
|
|
1039
|
+
this.#internalThis.process({ concurrency: 1, cancellationSignal: this.#cancellationSignal, forceDequeue: true, types: ['[SystemWorker]:FanIn'] }, async (context) => {
|
|
1040
|
+
await this.resolveDependencies(context.data.targetTags);
|
|
1041
|
+
return TaskProcessResult.Complete();
|
|
1032
1042
|
});
|
|
1033
1043
|
}
|
|
1034
1044
|
async *getBatchConsumer(size, cancellationSignal, options) {
|
|
@@ -1043,12 +1053,12 @@ let PostgresQueue = class PostgresQueue extends TaskQueue {
|
|
|
1043
1053
|
}
|
|
1044
1054
|
}
|
|
1045
1055
|
};
|
|
1046
|
-
|
|
1056
|
+
PostgresTaskQueue = __decorate([
|
|
1047
1057
|
Singleton({
|
|
1048
1058
|
argumentIdentityProvider: JSON.stringify,
|
|
1049
1059
|
providers: [
|
|
1050
1060
|
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresTaskQueueModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: 2 }) }),
|
|
1051
1061
|
],
|
|
1052
1062
|
})
|
|
1053
|
-
],
|
|
1054
|
-
export {
|
|
1063
|
+
], PostgresTaskQueue);
|
|
1064
|
+
export { PostgresTaskQueue };
|