@tstdl/base 0.93.77 → 0.93.78
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/http-client.middleware.js +2 -2
- package/authentication/models/authentication-credentials.model.d.ts +2 -2
- package/authentication/models/authentication-credentials.model.js +5 -3
- package/authentication/models/authentication-session.model.d.ts +2 -2
- package/authentication/models/authentication-session.model.js +5 -3
- package/authentication/models/index.d.ts +4 -0
- package/authentication/models/index.js +4 -0
- package/authentication/models/service-account.model.d.ts +7 -0
- package/authentication/models/service-account.model.js +31 -0
- package/authentication/models/subject.model.d.ts +16 -0
- package/authentication/models/subject.model.js +59 -0
- package/authentication/models/system-account.model.d.ts +5 -0
- package/authentication/models/system-account.model.js +25 -0
- package/authentication/models/user.model.d.ts +15 -0
- package/authentication/models/user.model.js +47 -0
- package/authentication/server/drizzle/0001_condemned_pretty_boy.sql +70 -0
- package/authentication/server/drizzle/meta/0001_snapshot.json +651 -0
- package/authentication/server/drizzle/meta/_journal.json +7 -0
- package/authentication/server/index.d.ts +1 -0
- package/authentication/server/index.js +1 -0
- package/authentication/server/schemas.d.ts +16 -1
- package/authentication/server/schemas.js +7 -1
- package/authentication/server/subject.service.d.ts +6 -0
- package/authentication/server/subject.service.js +44 -0
- package/circuit-breaker/circuit-breaker.d.ts +32 -0
- package/circuit-breaker/circuit-breaker.js +9 -0
- package/circuit-breaker/index.d.ts +2 -0
- package/circuit-breaker/index.js +2 -0
- package/circuit-breaker/postgres/circuit-breaker.d.ts +7 -0
- package/circuit-breaker/postgres/circuit-breaker.js +78 -0
- package/circuit-breaker/postgres/drizzle/0000_hard_shocker.sql +9 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +82 -0
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +13 -0
- package/circuit-breaker/postgres/drizzle.config.d.ts +2 -0
- package/circuit-breaker/postgres/drizzle.config.js +11 -0
- package/circuit-breaker/postgres/index.d.ts +5 -0
- package/circuit-breaker/postgres/index.js +5 -0
- package/circuit-breaker/postgres/model.d.ts +9 -0
- package/circuit-breaker/postgres/model.js +40 -0
- package/circuit-breaker/postgres/module.d.ts +6 -0
- package/circuit-breaker/postgres/module.js +25 -0
- package/circuit-breaker/postgres/provider.d.ts +6 -0
- package/circuit-breaker/postgres/provider.js +21 -0
- package/circuit-breaker/postgres/schemas.d.ts +8 -0
- package/circuit-breaker/postgres/schemas.js +6 -0
- package/circuit-breaker/provider.d.ts +4 -0
- package/circuit-breaker/provider.js +2 -0
- package/circuit-breaker/tests/circuit-breaker.test.js +113 -0
- package/document-management/models/document.model.d.ts +0 -1
- package/document-management/models/document.model.js +0 -5
- package/document-management/server/api/document-management.api.js +1 -2
- package/document-management/server/drizzle/0002_round_warbird.sql +1 -0
- package/document-management/server/drizzle/meta/0002_snapshot.json +2722 -0
- package/document-management/server/drizzle/meta/_journal.json +7 -0
- package/document-management/server/services/document-collection.service.js +3 -3
- package/document-management/server/services/document-management-ancillary.service.d.ts +1 -1
- package/document-management/server/services/document-management.service.js +1 -1
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/server/services/document.service.d.ts +0 -2
- package/document-management/server/services/document.service.js +1 -2
- package/document-management/service-models/enriched/enriched-document.view.d.ts +1 -1
- package/examples/document-management/main.d.ts +1 -1
- package/examples/document-management/main.js +1 -1
- package/logger/transports/console.d.ts +1 -1
- package/logger/transports/console.js +4 -1
- package/message-bus/message-bus-base.js +1 -1
- package/package.json +6 -3
- package/queue/enqueue-batch.d.ts +11 -11
- package/queue/enqueue-batch.js +2 -3
- package/queue/index.d.ts +1 -0
- package/queue/index.js +1 -0
- package/queue/postgres/drizzle/0003_tricky_venom.sql +30 -0
- package/queue/postgres/drizzle/meta/0003_snapshot.json +288 -0
- package/queue/postgres/drizzle/meta/_journal.json +7 -0
- package/queue/postgres/drizzle.config.js +2 -2
- package/queue/postgres/index.d.ts +1 -1
- package/queue/postgres/index.js +1 -1
- package/queue/postgres/module.d.ts +1 -1
- package/queue/postgres/module.js +1 -1
- package/queue/postgres/queue.d.ts +52 -23
- package/queue/postgres/queue.js +582 -64
- package/queue/postgres/queue.provider.d.ts +1 -1
- package/queue/postgres/schemas.d.ts +13 -2
- package/queue/postgres/schemas.js +4 -2
- package/queue/postgres/task.model.d.ts +24 -0
- package/queue/postgres/task.model.js +115 -0
- package/queue/provider.d.ts +1 -1
- package/queue/queue.d.ts +158 -37
- package/queue/queue.js +97 -19
- package/queue/task-context.d.ts +38 -0
- package/queue/task-context.js +102 -0
- package/queue/tests/queue.test.d.ts +1 -0
- package/queue/tests/queue.test.js +623 -0
- package/test4.d.ts +1 -1
- package/test4.js +1 -1
- package/utils/format-error.d.ts +17 -20
- package/utils/format-error.js +105 -47
- package/queue/postgres/job.model.d.ts +0 -12
- package/queue/postgres/job.model.js +0 -53
- package/test6.js +0 -33
- /package/{test6.d.ts → circuit-breaker/tests/circuit-breaker.test.d.ts} +0 -0
package/queue/postgres/queue.js
CHANGED
|
@@ -4,67 +4,106 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { and, asc, eq,
|
|
7
|
+
import { and, asc, eq, gt, inArray, lt, lte, or, sql, isNull as sqlIsNull } from 'drizzle-orm';
|
|
8
8
|
import { merge } from 'rxjs';
|
|
9
9
|
import { CancellationSignal } from '../../cancellation/index.js';
|
|
10
|
+
import { CircuitBreaker, CircuitBreakerState } from '../../circuit-breaker/index.js';
|
|
11
|
+
import { Enumerable } from '../../enumerable/enumerable.js';
|
|
10
12
|
import { inject, injectArgument, provide, Singleton } from '../../injector/index.js';
|
|
11
13
|
import { MessageBus } from '../../message-bus/index.js';
|
|
12
|
-
import { interval, RANDOM_UUID_V4, TRANSACTION_TIMESTAMP } from '../../orm/index.js';
|
|
14
|
+
import { coalesce, interval, RANDOM_UUID_V4, TRANSACTION_TIMESTAMP } from '../../orm/index.js';
|
|
13
15
|
import { DatabaseConfig, injectRepository } from '../../orm/server/index.js';
|
|
16
|
+
import { toArray } from '../../utils/array/array.js';
|
|
17
|
+
import { currentTimestamp } from '../../utils/date-time.js';
|
|
18
|
+
import { serializeError } from '../../utils/format-error.js';
|
|
14
19
|
import { cancelableTimeout } from '../../utils/timing.js';
|
|
15
|
-
import { isDefined, isString, isUndefined } from '../../utils/type-guards.js';
|
|
20
|
+
import { isDefined, isNotNull, isNull, isString, isUndefined } from '../../utils/type-guards.js';
|
|
16
21
|
import { millisecondsPerSecond } from '../../utils/units.js';
|
|
17
|
-
import { defaultQueueConfig, Queue, UniqueTagStrategy } from '../queue.js';
|
|
18
|
-
import { PostgresJob } from './job.model.js';
|
|
22
|
+
import { defaultQueueConfig, Queue, TaskState, UniqueTagStrategy } from '../queue.js';
|
|
19
23
|
import { PostgresQueueModuleConfig } from './module.js';
|
|
20
|
-
import {
|
|
24
|
+
import { task as taskTable } from './schemas.js';
|
|
25
|
+
import { PostgresTask } from './task.model.js';
|
|
21
26
|
let PostgresQueue = class PostgresQueue extends Queue {
|
|
22
|
-
#repository = injectRepository(
|
|
27
|
+
#repository = injectRepository(PostgresTask);
|
|
23
28
|
#config = injectArgument(this);
|
|
24
29
|
#queueName = isString(this.#config) ? this.#config : this.#config.name;
|
|
25
30
|
#messageBus = inject((MessageBus), `PostgresQueue:${this.#queueName}`);
|
|
26
|
-
#
|
|
31
|
+
#circuitBreaker = inject(CircuitBreaker, {
|
|
32
|
+
key: this.#queueName,
|
|
33
|
+
threshold: (isString(this.#config) ? undefined : this.#config.circuitBreakerThreshold) ?? defaultQueueConfig.circuitBreakerThreshold,
|
|
34
|
+
resetTimeout: (isString(this.#config) ? undefined : this.#config.circuitBreakerResetTimeout) ?? defaultQueueConfig.circuitBreakerResetTimeout,
|
|
35
|
+
});
|
|
27
36
|
processTimeout = (isString(this.#config) ? undefined : this.#config.processTimeout) ?? defaultQueueConfig.processTimeout;
|
|
37
|
+
executionTimeout = (isString(this.#config) ? undefined : this.#config.executionTimeout) ?? defaultQueueConfig.executionTimeout;
|
|
28
38
|
maxTries = (isString(this.#config) ? undefined : this.#config.maxTries) ?? defaultQueueConfig.maxTries;
|
|
39
|
+
retryDelayMinimum = (isString(this.#config) ? undefined : this.#config.retryDelayMinimum) ?? defaultQueueConfig.retryDelayMinimum;
|
|
40
|
+
retryDelayMaximum = (isString(this.#config) ? undefined : this.#config.retryDelayMaximum) ?? defaultQueueConfig.retryDelayMaximum;
|
|
41
|
+
retryDelayGrowth = (isString(this.#config) ? undefined : this.#config.retryDelayGrowth) ?? defaultQueueConfig.retryDelayGrowth;
|
|
42
|
+
retentionPeriod = (isString(this.#config) ? undefined : this.#config.retentionPeriod) ?? defaultQueueConfig.retentionPeriod;
|
|
43
|
+
globalConcurrency = (isString(this.#config) ? undefined : this.#config.globalConcurrency) ?? defaultQueueConfig.globalConcurrency;
|
|
44
|
+
// -- Updates --
|
|
45
|
+
#keepOldUpdate = { id: sql `${taskTable.id}` };
|
|
29
46
|
#takeNewUpdate = {
|
|
30
47
|
id: RANDOM_UUID_V4,
|
|
31
48
|
queue: this.#queueName,
|
|
32
49
|
priority: sql `excluded.priority`,
|
|
33
50
|
tag: sql `excluded.tag`,
|
|
51
|
+
status: TaskState.Pending,
|
|
52
|
+
lease: null,
|
|
34
53
|
tries: 0,
|
|
54
|
+
progress: 0,
|
|
35
55
|
enqueueTimestamp: TRANSACTION_TIMESTAMP,
|
|
36
|
-
|
|
56
|
+
scheduleTimestamp: sql `excluded.schedule_timestamp`,
|
|
57
|
+
startTimestamp: null,
|
|
58
|
+
expirationTimestamp: sql `excluded.expiration_timestamp`,
|
|
59
|
+
lockExpirationTimestamp: null,
|
|
37
60
|
data: sql `excluded.data`,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
tries: sql `${job.tries} + 1`,
|
|
42
|
-
lastDequeueTimestamp: TRANSACTION_TIMESTAMP,
|
|
61
|
+
state: null,
|
|
62
|
+
error: null,
|
|
63
|
+
result: null,
|
|
43
64
|
};
|
|
44
65
|
async enqueue(data, options) {
|
|
45
|
-
const
|
|
46
|
-
return
|
|
66
|
+
const tasks = await this.enqueueMany([{ data, ...options }], { uniqueTag: options?.uniqueTag, returnTasks: true, transaction: options?.transaction });
|
|
67
|
+
return tasks[0];
|
|
47
68
|
}
|
|
48
69
|
async enqueueMany(items, options) {
|
|
70
|
+
if (items.length == 0) {
|
|
71
|
+
return (options?.returnTasks == true) ? [] : undefined;
|
|
72
|
+
}
|
|
49
73
|
const newEntities = items.map((item) => ({
|
|
50
74
|
queue: this.#queueName,
|
|
75
|
+
status: TaskState.Pending,
|
|
76
|
+
lease: null,
|
|
51
77
|
priority: item.priority ?? 1000,
|
|
52
78
|
tag: item.tag ?? null,
|
|
79
|
+
parentId: item.parentId ?? null,
|
|
53
80
|
tries: 0,
|
|
81
|
+
progress: 0,
|
|
54
82
|
enqueueTimestamp: TRANSACTION_TIMESTAMP,
|
|
55
|
-
|
|
83
|
+
scheduleTimestamp: item.scheduleTimestamp ?? TRANSACTION_TIMESTAMP,
|
|
84
|
+
startTimestamp: null,
|
|
85
|
+
expirationTimestamp: item.expirationTimestamp ?? null,
|
|
86
|
+
lockExpirationTimestamp: null,
|
|
87
|
+
completeTimestamp: null,
|
|
56
88
|
data: item.data,
|
|
89
|
+
state: null,
|
|
90
|
+
result: null,
|
|
91
|
+
error: null,
|
|
57
92
|
}));
|
|
58
93
|
const update = (options?.uniqueTag == UniqueTagStrategy.TakeNew)
|
|
59
94
|
? this.#takeNewUpdate
|
|
60
95
|
: (options?.uniqueTag == UniqueTagStrategy.KeepOld)
|
|
61
96
|
? this.#keepOldUpdate
|
|
62
97
|
: undefined;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
98
|
+
const repository = this.#repository.withOptionalTransaction(options?.transaction);
|
|
99
|
+
const tasks = isUndefined(update)
|
|
100
|
+
? await repository.insertMany(newEntities)
|
|
101
|
+
: await repository.upsertMany(['queue', 'tag'], newEntities, update);
|
|
66
102
|
this.#messageBus.publishAndForget();
|
|
67
|
-
|
|
103
|
+
if (options?.returnTasks == true) {
|
|
104
|
+
return tasks;
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
68
107
|
}
|
|
69
108
|
async has(id) {
|
|
70
109
|
return await this.#repository.hasByQuery({ queue: this.#queueName, id });
|
|
@@ -81,60 +120,539 @@ let PostgresQueue = class PostgresQueue extends Queue {
|
|
|
81
120
|
async getByTags(tags) {
|
|
82
121
|
return await this.#repository.loadManyByQuery({ queue: this.#queueName, tag: { $in: tags } });
|
|
83
122
|
}
|
|
84
|
-
async
|
|
85
|
-
|
|
123
|
+
async getTree(idOrIds, transaction) {
|
|
124
|
+
const ids = toArray(idOrIds);
|
|
125
|
+
return await this.getTreeByQuery({ id: { $in: ids } }, transaction);
|
|
86
126
|
}
|
|
87
|
-
async
|
|
88
|
-
await this.#repository.
|
|
127
|
+
async getTreeByQuery(query, transaction) {
|
|
128
|
+
return await this.#repository.useTransaction(transaction, async (tx) => {
|
|
129
|
+
const repositoryWithTransaction = this.#repository.withTransaction(tx);
|
|
130
|
+
const tasks = await repositoryWithTransaction.loadManyByQuery(query);
|
|
131
|
+
if (tasks.length == 0) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
let currentLevelIds = tasks.map((task) => task.id);
|
|
135
|
+
let depth = 0;
|
|
136
|
+
while (true) {
|
|
137
|
+
if (depth++ > 100) {
|
|
138
|
+
throw new Error('Possible cyclic task parent-child relationship detected in queue "' + this.#queueName + '"');
|
|
139
|
+
}
|
|
140
|
+
const childTasks = await repositoryWithTransaction.loadManyByQuery({ parentId: { $in: currentLevelIds } });
|
|
141
|
+
if (childTasks.length == 0) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
currentLevelIds = childTasks.map((task) => task.id);
|
|
145
|
+
tasks.push(...childTasks);
|
|
146
|
+
}
|
|
147
|
+
return tasks;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async cancel(id, transaction) {
|
|
151
|
+
await this.cancelMany([id], transaction);
|
|
152
|
+
}
|
|
153
|
+
async cancelMany(ids, transaction) {
|
|
154
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
155
|
+
const tree = await this.getTree(ids, tx);
|
|
156
|
+
const treeIds = tree.map((task) => task.id);
|
|
157
|
+
if (treeIds.length == 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
await this.#repository.withTransaction(tx).updateMany(treeIds, {
|
|
161
|
+
status: TaskState.Cancelled,
|
|
162
|
+
lease: null,
|
|
163
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
164
|
+
});
|
|
165
|
+
const uniqueParents = new Set();
|
|
166
|
+
for (const t of tree) {
|
|
167
|
+
const task = t;
|
|
168
|
+
if (isNotNull(task.parentId)) {
|
|
169
|
+
uniqueParents.add(task.parentId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const parentId of uniqueParents) {
|
|
173
|
+
await this.#triggerParentFanIn(parentId, tx);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
89
176
|
}
|
|
90
177
|
async cancelByTag(tag) {
|
|
91
|
-
await this
|
|
178
|
+
await this.cancelManyByTag([tag]);
|
|
92
179
|
}
|
|
93
|
-
async
|
|
94
|
-
await this.#repository.
|
|
180
|
+
async cancelManyByTag(tags) {
|
|
181
|
+
await this.#repository.transaction(async (tx) => {
|
|
182
|
+
const task = await this.getByTags(tags);
|
|
183
|
+
const ids = task.map((t) => t.id);
|
|
184
|
+
await this.cancelMany(ids, tx);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async clear() {
|
|
188
|
+
await this.#repository.hardDeleteManyByQuery({ queue: this.#queueName });
|
|
95
189
|
}
|
|
96
190
|
async dequeue() {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
191
|
+
const tasks = await this.dequeueMany(1);
|
|
192
|
+
if (tasks.length == 0) {
|
|
99
193
|
return undefined;
|
|
100
194
|
}
|
|
101
|
-
return
|
|
195
|
+
return tasks[0];
|
|
102
196
|
}
|
|
103
197
|
async dequeueMany(count) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
198
|
+
return await this.#repository.transaction(async (tx) => {
|
|
199
|
+
let effectiveCount = count;
|
|
200
|
+
// 1. Check Circuit Breaker
|
|
201
|
+
const result = await this.#circuitBreaker.check();
|
|
202
|
+
if (!result.allowed) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
if (result.state == CircuitBreakerState.HalfOpen) {
|
|
206
|
+
// If we are probing (HalfOpen), we must ensure no other tasks are running locally.
|
|
207
|
+
// NOTE: PostgresCircuitBreakerService.check() handles the "one probe per timeout" via the DB state transition.
|
|
208
|
+
// However, we still might want to limit this batch to 1 task if we are the probe.
|
|
209
|
+
if (result.isProbe != true) {
|
|
210
|
+
const runningCount = await this.#repository.withTransaction(tx).countByQuery({ queue: this.#queueName, status: TaskState.Running });
|
|
211
|
+
if (runningCount > 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
effectiveCount = 1;
|
|
216
|
+
}
|
|
217
|
+
// 2. Check Global Concurrency
|
|
218
|
+
if (isNotNull(this.globalConcurrency)) {
|
|
219
|
+
const runningCount = await this.#repository.withTransaction(tx).countByQuery({ queue: this.#queueName, status: TaskState.Running });
|
|
220
|
+
if (runningCount >= this.globalConcurrency) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/*
|
|
225
|
+
* Materialization required for LIMIT clause
|
|
226
|
+
* https://stackoverflow.com/questions/73966670/select-for-update-subquery-not-respecting-limit-clause-under-load
|
|
227
|
+
* https://dba.stackexchange.com/questions/69471/postgres-update-limit-1
|
|
228
|
+
*/
|
|
229
|
+
const selection = this.#repository.session.$with('selection').as((qb) => qb
|
|
230
|
+
.select({ id: taskTable.id })
|
|
231
|
+
.from(taskTable)
|
|
232
|
+
.where(and(eq(taskTable.queue, this.#queueName), lte(taskTable.scheduleTimestamp, TRANSACTION_TIMESTAMP), or(sqlIsNull(taskTable.expirationTimestamp), lt(TRANSACTION_TIMESTAMP, taskTable.expirationTimestamp)), or(eq(taskTable.status, TaskState.Pending), and(eq(taskTable.status, TaskState.Running), lt(taskTable.lockExpirationTimestamp, TRANSACTION_TIMESTAMP), // Zombie detection (only non-exhausted ones)
|
|
233
|
+
lt(taskTable.tries, this.maxTries))), sql `pg_sleep(0) IS NOT NULL` // Materialization hack until drizzle implements https://github.com/drizzle-team/drizzle-orm/issues/2318
|
|
234
|
+
))
|
|
235
|
+
.orderBy(asc(taskTable.priority), asc(taskTable.scheduleTimestamp), asc(taskTable.tries))
|
|
236
|
+
.limit(effectiveCount)
|
|
237
|
+
.for('update', { skipLocked: true }));
|
|
238
|
+
const rows = await this.#repository.session
|
|
239
|
+
.with(selection)
|
|
240
|
+
.update(taskTable)
|
|
241
|
+
.set({
|
|
242
|
+
status: TaskState.Running,
|
|
243
|
+
lease: RANDOM_UUID_V4,
|
|
244
|
+
lockExpirationTimestamp: sql `${TRANSACTION_TIMESTAMP} + ${interval(this.processTimeout, 'milliseconds')}`,
|
|
245
|
+
startTimestamp: TRANSACTION_TIMESTAMP,
|
|
246
|
+
// If it was PENDING, it's the first try (tries=0) -> tries=1.
|
|
247
|
+
// If it was RUNNING (Zombie), previous try failed -> increment tries.
|
|
248
|
+
tries: sql `${taskTable.tries} + 1`,
|
|
249
|
+
})
|
|
250
|
+
.where(inArray(taskTable.id, this.#repository.session.select().from(selection)))
|
|
251
|
+
.returning();
|
|
252
|
+
return await this.#repository.mapManyToEntity(rows);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async reschedule(id, timestamp, transaction) {
|
|
256
|
+
await this.rescheduleMany([id], timestamp, transaction);
|
|
257
|
+
}
|
|
258
|
+
async rescheduleMany(ids, timestamp, transaction) {
|
|
259
|
+
await this.#repository.withOptionalTransaction(transaction).updateMany(ids, {
|
|
260
|
+
status: TaskState.Pending,
|
|
261
|
+
lease: null,
|
|
262
|
+
scheduleTimestamp: timestamp,
|
|
263
|
+
lockExpirationTimestamp: null,
|
|
264
|
+
tries: sql `CASE
|
|
265
|
+
WHEN ${taskTable.status} = ${TaskState.Running} THEN GREATEST(0, ${taskTable.tries} - 1)
|
|
266
|
+
ELSE ${taskTable.tries}
|
|
267
|
+
END`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async rescheduleByTag(tag, timestamp, transaction) {
|
|
271
|
+
await this.rescheduleManyByTag([tag], timestamp, transaction);
|
|
272
|
+
}
|
|
273
|
+
async rescheduleManyByTag(tags, timestamp, transaction) {
|
|
274
|
+
await this.#repository.withOptionalTransaction(transaction).updateManyByQuery({
|
|
275
|
+
queue: this.#queueName,
|
|
276
|
+
tag: { $in: tags },
|
|
277
|
+
}, {
|
|
278
|
+
status: TaskState.Pending,
|
|
279
|
+
lease: null,
|
|
280
|
+
scheduleTimestamp: timestamp,
|
|
281
|
+
lockExpirationTimestamp: null,
|
|
282
|
+
tries: sql `CASE
|
|
283
|
+
WHEN ${taskTable.status} = ${TaskState.Running} THEN GREATEST(0, ${taskTable.tries} - 1)
|
|
284
|
+
ELSE ${taskTable.tries}
|
|
285
|
+
END`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async touch(task, options) {
|
|
289
|
+
if (isNull(task.lease)) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
return await this.#repository.useTransaction(options?.transaction, async (tx) => {
|
|
293
|
+
const update = {
|
|
294
|
+
lockExpirationTimestamp: sql `${TRANSACTION_TIMESTAMP} + ${interval(this.processTimeout, 'milliseconds')}`,
|
|
295
|
+
};
|
|
296
|
+
if (isDefined(options?.progress)) {
|
|
297
|
+
update.progress = options.progress;
|
|
298
|
+
}
|
|
299
|
+
if (isDefined(options?.state)) {
|
|
300
|
+
update.state = options.state;
|
|
301
|
+
}
|
|
302
|
+
// Attempt to update lease.
|
|
303
|
+
// FAILS (returns undefined) if lease has changed (timeout/cancel/stolen)
|
|
304
|
+
// OR if hard execution timeout has passed.
|
|
305
|
+
const result = await this.#repository.withTransaction(tx).tryUpdateByQuery({
|
|
306
|
+
queue: this.#queueName,
|
|
307
|
+
id: task.id,
|
|
308
|
+
status: TaskState.Running,
|
|
309
|
+
lease: task.lease,
|
|
310
|
+
startTimestamp: { $gt: sql `${TRANSACTION_TIMESTAMP} - ${interval(this.executionTimeout, 'milliseconds')}` },
|
|
311
|
+
}, update);
|
|
312
|
+
return result;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async touchMany(tasks, progresses, states, transaction) {
|
|
316
|
+
if (tasks.length == 0) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
const rows = tasks.map((t, i) => {
|
|
320
|
+
const progress = progresses?.[i] ?? null;
|
|
321
|
+
const state = states?.[i] ?? null;
|
|
322
|
+
return sql `(${t.id}::uuid, ${t.lease}::uuid, ${progress}::numeric, ${state}::jsonb)`;
|
|
323
|
+
});
|
|
324
|
+
const updates = this.#repository.session.$with('updates').as((qb) => qb
|
|
325
|
+
.select({
|
|
326
|
+
updateId: sql `(id)::uuid`.as('update_id'),
|
|
327
|
+
updateLease: sql `(lease)::uuid`.as('update_lease'),
|
|
328
|
+
updateProgress: sql `(progress)::numeric`.as('update_progress'),
|
|
329
|
+
updateState: sql `(state)::jsonb`.as('update_state'),
|
|
330
|
+
})
|
|
331
|
+
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, lease, progress, state)`));
|
|
332
|
+
const updated = this.#repository.session.$with('updated').as(() => this.#repository.session
|
|
333
|
+
.update(taskTable)
|
|
334
|
+
.set({
|
|
335
|
+
lockExpirationTimestamp: sql `${TRANSACTION_TIMESTAMP} + ${interval(this.processTimeout, 'milliseconds')}`,
|
|
336
|
+
progress: coalesce(updates.updateProgress, taskTable.progress),
|
|
337
|
+
state: coalesce(updates.updateState, taskTable['state']),
|
|
338
|
+
})
|
|
339
|
+
.from(updates)
|
|
340
|
+
.where(and(eq(taskTable.id, updates.updateId), eq(taskTable.queue, this.#queueName), eq(taskTable.lease, updates.updateLease), eq(taskTable.status, TaskState.Running), gt(taskTable.startTimestamp, sql `${TRANSACTION_TIMESTAMP} - ${interval(this.executionTimeout, 'milliseconds')}`)))
|
|
341
|
+
.returning({ id: taskTable.id }));
|
|
342
|
+
const result = await this.#repository.withOptionalTransaction(transaction).session
|
|
343
|
+
.with(updates, updated)
|
|
344
|
+
.select({ id: updated.id })
|
|
345
|
+
.from(updated)
|
|
346
|
+
.execute();
|
|
347
|
+
return result.map((r) => r.id);
|
|
348
|
+
}
|
|
349
|
+
async acknowledge(task, result, transaction, { skipFanIn = false } = {}) {
|
|
350
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
351
|
+
const repository = this.#repository.withTransaction(tx);
|
|
352
|
+
// 1. Fan-Out Check: Does this task have active children?
|
|
353
|
+
// If yes, this is a Parent task that has spawned children.
|
|
354
|
+
// It should enter 'Waiting' instead of 'Completed'.
|
|
355
|
+
const activeChildrenCount = await repository.countByQuery({
|
|
356
|
+
parentId: task.id,
|
|
357
|
+
status: { $nin: [TaskState.Completed, TaskState.Cancelled, TaskState.Dead] },
|
|
358
|
+
});
|
|
359
|
+
let updatedTask;
|
|
360
|
+
if (activeChildrenCount > 0) {
|
|
361
|
+
updatedTask = await repository.tryUpdateByQuery({
|
|
362
|
+
id: task.id,
|
|
363
|
+
lease: task.lease,
|
|
364
|
+
}, {
|
|
365
|
+
status: TaskState.Waiting,
|
|
366
|
+
lease: null,
|
|
367
|
+
lockExpirationTimestamp: null,
|
|
368
|
+
state: result,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
updatedTask = await repository.tryUpdateByQuery({
|
|
373
|
+
id: task.id,
|
|
374
|
+
lease: task.lease,
|
|
375
|
+
}, {
|
|
376
|
+
status: TaskState.Completed,
|
|
377
|
+
lease: null,
|
|
378
|
+
result: result,
|
|
379
|
+
progress: 1,
|
|
380
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
381
|
+
lockExpirationTimestamp: null,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (isUndefined(updatedTask)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// 2. Reset Circuit Breaker (Success detected)
|
|
388
|
+
await this.#circuitBreaker.recordSuccess();
|
|
389
|
+
// 3. Fan-In: Wake up parent if all siblings are done
|
|
390
|
+
if (!skipFanIn && isNotNull(task.parentId)) {
|
|
391
|
+
await this.#triggerParentFanIn(task.parentId, tx);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async acknowledgeMany(tasks, results, transaction) {
|
|
396
|
+
if (tasks.length == 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
400
|
+
// 1. Bulk Check for Active Children
|
|
401
|
+
const taskIds = tasks.map((task) => task.id);
|
|
402
|
+
const parentsWithChildrenRows = await this.#repository.withTransaction(tx).session
|
|
403
|
+
.selectDistinct({ id: taskTable.parentId }) // Distinct is cleaner
|
|
404
|
+
.from(taskTable)
|
|
405
|
+
.where(and(inArray(taskTable.parentId, taskIds), inArray(taskTable.status, [TaskState.Pending, TaskState.Running, TaskState.Waiting])))
|
|
406
|
+
.execute();
|
|
407
|
+
const allParentIds = parentsWithChildrenRows.map((row) => row.id);
|
|
408
|
+
const distinctParentIds = new Set(allParentIds);
|
|
409
|
+
// 2. Separate tasks
|
|
410
|
+
const tasksToWait = [];
|
|
411
|
+
const tasksToComplete = [];
|
|
412
|
+
const resultsToComplete = [];
|
|
413
|
+
const resultsToWait = [];
|
|
414
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
415
|
+
const task = tasks[i];
|
|
416
|
+
if (distinctParentIds.has(task.id)) {
|
|
417
|
+
tasksToWait.push(task);
|
|
418
|
+
if (isDefined(results)) {
|
|
419
|
+
resultsToWait.push(results[i]);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
tasksToComplete.push(task);
|
|
424
|
+
if (isDefined(results)) {
|
|
425
|
+
resultsToComplete.push(results[i]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// 3. Update 'Waiting' tasks
|
|
430
|
+
if (tasksToWait.length > 0) {
|
|
431
|
+
const rows = tasksToWait.map((task, i) => {
|
|
432
|
+
const result = isDefined(results) ? resultsToWait[i] : null;
|
|
433
|
+
return sql `(${task.id}::uuid, ${task.lease}::uuid, ${result}::jsonb)`;
|
|
434
|
+
});
|
|
435
|
+
const updates = this.#repository.session.$with('updates').as((qb) => qb
|
|
436
|
+
.select({
|
|
437
|
+
updateId: sql `(id)::uuid`.as('update_id'),
|
|
438
|
+
updateLease: sql `(lease)::uuid`.as('update_lease'),
|
|
439
|
+
updateResult: sql `(result)::jsonb`.as('update_result'),
|
|
440
|
+
})
|
|
441
|
+
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, lease, result)`));
|
|
442
|
+
const updated = this.#repository.session.$with('updated').as(() => this.#repository.session
|
|
443
|
+
.update(taskTable)
|
|
444
|
+
.set({
|
|
445
|
+
status: TaskState.Waiting,
|
|
446
|
+
lease: null,
|
|
447
|
+
lockExpirationTimestamp: null,
|
|
448
|
+
result: updates.updateResult,
|
|
449
|
+
})
|
|
450
|
+
.from(updates)
|
|
451
|
+
.where(and(eq(taskTable.id, updates.updateId), eq(taskTable.lease, updates.updateLease), eq(taskTable.queue, this.#queueName)))
|
|
452
|
+
.returning({ id: taskTable.id }));
|
|
453
|
+
await this.#repository.withTransaction(tx).session
|
|
454
|
+
.with(updates, updated)
|
|
455
|
+
.select({ id: updated.id })
|
|
456
|
+
.from(updated)
|
|
457
|
+
.execute();
|
|
458
|
+
}
|
|
459
|
+
// 4. Update 'Completed' tasks
|
|
460
|
+
if (tasksToComplete.length > 0) {
|
|
461
|
+
const rows = tasksToComplete.map((t, i) => {
|
|
462
|
+
const result = isDefined(results) ? resultsToComplete[i] : null;
|
|
463
|
+
return sql `(${t.id}::uuid, ${t.lease}::uuid, ${result}::jsonb)`;
|
|
464
|
+
});
|
|
465
|
+
const updates = this.#repository.session.$with('updates').as((qb) => qb
|
|
466
|
+
.select({
|
|
467
|
+
updateId: sql `(id)::uuid`.as('update_id'),
|
|
468
|
+
updateLease: sql `(lease)::uuid`.as('update_lease'),
|
|
469
|
+
updateResult: sql `(result)::jsonb`.as('update_result'),
|
|
470
|
+
})
|
|
471
|
+
.from(sql `(VALUES ${sql.join(rows, sql `, `)}) AS t(id, lease, result)`));
|
|
472
|
+
const updated = this.#repository.session.$with('updated').as(() => this.#repository.session
|
|
473
|
+
.update(taskTable)
|
|
474
|
+
.set({
|
|
475
|
+
status: TaskState.Completed,
|
|
476
|
+
lease: null,
|
|
477
|
+
result: updates.updateResult,
|
|
478
|
+
progress: 1,
|
|
479
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
480
|
+
lockExpirationTimestamp: null,
|
|
481
|
+
})
|
|
482
|
+
.from(updates)
|
|
483
|
+
.where(and(eq(taskTable.id, updates.updateId), eq(taskTable.lease, updates.updateLease), eq(taskTable.queue, this.#queueName)))
|
|
484
|
+
.returning({ id: taskTable.id }));
|
|
485
|
+
await this.#repository.withTransaction(tx).session
|
|
486
|
+
.with(updates, updated)
|
|
487
|
+
.select({ id: updated.id })
|
|
488
|
+
.from(updated)
|
|
489
|
+
.execute();
|
|
490
|
+
// Reset circuit breaker
|
|
491
|
+
await this.#circuitBreaker.recordSuccess();
|
|
492
|
+
}
|
|
493
|
+
// 5. Fan-In
|
|
494
|
+
const parentIds = Enumerable.from(tasks)
|
|
495
|
+
.filter((task) => isNotNull(task.parentId))
|
|
496
|
+
.map((task) => task.parentId)
|
|
497
|
+
.distinct();
|
|
498
|
+
for (const parentId of parentIds) {
|
|
499
|
+
await this.#triggerParentFanIn(parentId, tx);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
async fail(task, error, fatal = false, transaction, { skipFanIn = false } = {}) {
|
|
504
|
+
const isRetryable = !fatal && (task.tries < this.maxTries);
|
|
505
|
+
const nextStatus = isRetryable ? TaskState.Pending : TaskState.Dead;
|
|
506
|
+
const delay = isRetryable
|
|
507
|
+
? Math.min(this.retryDelayMaximum, this.retryDelayMinimum * (this.retryDelayGrowth ** task.tries))
|
|
508
|
+
: 0;
|
|
509
|
+
const nextSchedule = currentTimestamp() + delay;
|
|
510
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
511
|
+
const updatedTask = await this.#repository.withTransaction(tx).tryUpdateByQuery({
|
|
512
|
+
queue: this.#queueName,
|
|
513
|
+
id: task.id,
|
|
514
|
+
lease: task.lease,
|
|
515
|
+
tries: task.tries,
|
|
516
|
+
}, {
|
|
517
|
+
status: nextStatus,
|
|
518
|
+
lease: null,
|
|
519
|
+
error: serializeError(error),
|
|
520
|
+
lockExpirationTimestamp: null,
|
|
521
|
+
scheduleTimestamp: nextSchedule,
|
|
522
|
+
startTimestamp: null,
|
|
523
|
+
completeTimestamp: (nextStatus == TaskState.Dead) ? TRANSACTION_TIMESTAMP : null,
|
|
524
|
+
});
|
|
525
|
+
if (isUndefined(updatedTask)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
await this.#circuitBreaker.recordFailure();
|
|
529
|
+
if (!skipFanIn && (nextStatus == TaskState.Dead) && isNotNull(task.parentId)) {
|
|
530
|
+
await this.#triggerParentFanIn(task.parentId, tx);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
async #triggerParentFanIn(parentId, transaction) {
|
|
535
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
536
|
+
// 1. Lock Parent
|
|
537
|
+
const [parent] = await this.#repository.withTransaction(tx).session
|
|
538
|
+
.select({ id: taskTable.id, status: taskTable.status })
|
|
539
|
+
.from(taskTable)
|
|
540
|
+
.where(eq(taskTable.id, parentId))
|
|
541
|
+
.for('update')
|
|
542
|
+
.execute();
|
|
543
|
+
if (parent?.status != TaskState.Waiting) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// 2. Check Children
|
|
547
|
+
const hasActiveChildren = await this.#repository.withTransaction(tx).hasByQuery({
|
|
548
|
+
parentId,
|
|
549
|
+
status: { $in: [TaskState.Pending, TaskState.Running, TaskState.Waiting] },
|
|
550
|
+
});
|
|
551
|
+
if (!hasActiveChildren) {
|
|
552
|
+
// 3. Update Parent
|
|
553
|
+
await this.#repository.withTransaction(tx).updateByQuery({ id: parentId }, {
|
|
554
|
+
status: TaskState.Pending,
|
|
555
|
+
scheduleTimestamp: TRANSACTION_TIMESTAMP,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
async failMany(tasks, errors, transaction) {
|
|
561
|
+
if (tasks.length == 0) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
await this.#repository.useTransaction(transaction, async (tx) => {
|
|
565
|
+
await Promise.all(tasks.map(async (task, index) => await this.fail(task, errors[index], false, tx, { skipFanIn: true })));
|
|
566
|
+
const parentIds = Enumerable.from(tasks)
|
|
567
|
+
.filter((task) => isNotNull(task.parentId))
|
|
568
|
+
.map((task) => task.parentId)
|
|
569
|
+
.distinct();
|
|
570
|
+
for (const parentId of parentIds) {
|
|
571
|
+
await this.#triggerParentFanIn(parentId, tx);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
async prune() {
|
|
576
|
+
// 1. Clean up old finished tasks
|
|
577
|
+
await this.#repository.deleteManyByQuery({
|
|
578
|
+
queue: this.#queueName,
|
|
579
|
+
status: { $in: [TaskState.Completed, TaskState.Cancelled, TaskState.Dead] },
|
|
580
|
+
completeTimestamp: { $lt: sql `${TRANSACTION_TIMESTAMP} - ${interval(this.retentionPeriod, 'milliseconds')}` },
|
|
581
|
+
});
|
|
582
|
+
await this.#repository.transaction(async (tx) => {
|
|
583
|
+
// 2. Fail tasks that sat in Pending state past their expiration
|
|
584
|
+
const expiredTasks = await this.#repository.updateManyByQuery({
|
|
585
|
+
queue: this.#queueName,
|
|
586
|
+
status: TaskState.Pending,
|
|
587
|
+
expirationTimestamp: { $lt: TRANSACTION_TIMESTAMP },
|
|
588
|
+
}, {
|
|
589
|
+
status: TaskState.Dead,
|
|
590
|
+
lease: null,
|
|
591
|
+
error: { message: 'Queue Timeout: Task expired before processing' },
|
|
592
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
593
|
+
});
|
|
594
|
+
// 3. Fail "Rotting Zombies" (Tasks that crashed workers and exceeded max retries)
|
|
595
|
+
// These were excluded from dequeueMany to prevent infinite loops, so we capture them here.
|
|
596
|
+
const zombieTasks = await this.#repository.updateManyByQuery({
|
|
597
|
+
queue: this.#queueName,
|
|
598
|
+
status: TaskState.Running,
|
|
599
|
+
lockExpirationTimestamp: { $lt: TRANSACTION_TIMESTAMP },
|
|
600
|
+
tries: { $gte: this.maxTries },
|
|
601
|
+
}, {
|
|
602
|
+
status: TaskState.Dead,
|
|
603
|
+
lease: null,
|
|
604
|
+
error: { message: 'Zombie Task: Exceeded max retries after repeated crashes' },
|
|
605
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
606
|
+
lockExpirationTimestamp: null,
|
|
607
|
+
});
|
|
608
|
+
// 4. Fail tasks that exceeded hard execution timeout
|
|
609
|
+
const hardTimedoutTasks = await this.#repository.updateManyByQuery({
|
|
610
|
+
queue: this.#queueName,
|
|
611
|
+
status: TaskState.Running,
|
|
612
|
+
startTimestamp: { $lt: sql `${TRANSACTION_TIMESTAMP} - ${interval(this.executionTimeout, 'milliseconds')}` },
|
|
613
|
+
}, {
|
|
614
|
+
status: TaskState.Dead,
|
|
615
|
+
lease: null,
|
|
616
|
+
error: { message: `Hard Execution Timeout: Task ran longer than ${this.executionTimeout}ms` },
|
|
617
|
+
completeTimestamp: TRANSACTION_TIMESTAMP,
|
|
618
|
+
lockExpirationTimestamp: null,
|
|
619
|
+
});
|
|
620
|
+
const distinctParentTaskIds = Enumerable
|
|
621
|
+
.from(expiredTasks)
|
|
622
|
+
.concat(zombieTasks, hardTimedoutTasks)
|
|
623
|
+
.filter((task) => isNotNull(task.parentId))
|
|
624
|
+
.map((task) => task.parentId)
|
|
625
|
+
.distinct();
|
|
626
|
+
// 3. Trigger Fan-In for all affected parents
|
|
627
|
+
for (const parentId of distinctParentTaskIds) {
|
|
628
|
+
await this.#triggerParentFanIn(parentId, tx);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
async restart(id, transaction) {
|
|
633
|
+
await this.#repository.withOptionalTransaction(transaction).updateByQuery({
|
|
634
|
+
id,
|
|
635
|
+
$or: [
|
|
636
|
+
{ status: { $ne: TaskState.Running } },
|
|
637
|
+
{ lockExpirationTimestamp: { $lt: TRANSACTION_TIMESTAMP } },
|
|
638
|
+
],
|
|
639
|
+
}, {
|
|
640
|
+
status: TaskState.Pending,
|
|
641
|
+
lease: null,
|
|
642
|
+
error: null,
|
|
643
|
+
scheduleTimestamp: TRANSACTION_TIMESTAMP,
|
|
644
|
+
tries: 0,
|
|
645
|
+
progress: 0,
|
|
646
|
+
result: null,
|
|
647
|
+
completeTimestamp: null,
|
|
648
|
+
});
|
|
131
649
|
}
|
|
132
650
|
async *getConsumer(cancellationSignal) {
|
|
133
651
|
const continue$ = merge(this.#messageBus.allMessages$, cancellationSignal);
|
|
134
652
|
while (cancellationSignal.isUnset) {
|
|
135
|
-
const
|
|
136
|
-
if (isDefined(
|
|
137
|
-
yield
|
|
653
|
+
const task = await this.dequeue();
|
|
654
|
+
if (isDefined(task)) {
|
|
655
|
+
yield task;
|
|
138
656
|
continue;
|
|
139
657
|
}
|
|
140
658
|
await cancelableTimeout(5 * millisecondsPerSecond, continue$);
|
|
@@ -143,9 +661,9 @@ let PostgresQueue = class PostgresQueue extends Queue {
|
|
|
143
661
|
async *getBatchConsumer(size, cancellationSignal) {
|
|
144
662
|
const continue$ = merge(this.#messageBus.allMessages$, cancellationSignal);
|
|
145
663
|
while (cancellationSignal.isUnset) {
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
yield
|
|
664
|
+
const tasks = await this.dequeueMany(size);
|
|
665
|
+
if (tasks.length > 0) {
|
|
666
|
+
yield tasks;
|
|
149
667
|
continue;
|
|
150
668
|
}
|
|
151
669
|
await cancelableTimeout(5 * millisecondsPerSecond, continue$);
|