@dbos-inc/dbos-sdk 4.26.7-preview → 4.26.8-preview
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/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +0 -2
- package/dist/src/client.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +12 -7
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +84 -38
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/enqueue_options.d.ts.map +1 -1
- package/dist/src/enqueue_options.js +0 -1
- package/dist/src/enqueue_options.js.map +1 -1
- package/dist/src/error.d.ts +2 -1
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js +4 -4
- package/dist/src/error.js.map +1 -1
- package/dist/src/scheduler/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler/scheduler.js +0 -1
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/system_database.d.ts +11 -6
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +107 -94
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/wfqueue.d.ts.map +1 -1
- package/dist/src/wfqueue.js +1 -8
- package/dist/src/wfqueue.js.map +1 -1
- package/dist/src/workflow_management.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -564,14 +564,12 @@ class SystemDatabase {
|
|
|
564
564
|
await this.pool.end();
|
|
565
565
|
}
|
|
566
566
|
// ==================== Workflow Status ====================
|
|
567
|
-
async initWorkflowStatus(initStatus, ownerXid
|
|
567
|
+
async initWorkflowStatus(initStatus, ownerXid) {
|
|
568
568
|
const client = await this.pool.connect();
|
|
569
569
|
let shouldCommit = false;
|
|
570
570
|
try {
|
|
571
571
|
await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
|
|
572
|
-
|
|
573
|
-
// where it moves from pending back to enqueued.
|
|
574
|
-
const resRow = await this.insertWorkflowStatus(client, initStatus, ownerXid, !!options?.isRecoveryRequest || !!options?.isDequeuedRequest);
|
|
572
|
+
const resRow = await this.insertWorkflowStatus(client, initStatus, ownerXid);
|
|
575
573
|
if (resRow.name !== initStatus.workflowName) {
|
|
576
574
|
const msg = `Workflow already exists with a different function name: ${resRow.name}, but the provided function name is: ${initStatus.workflowName}`;
|
|
577
575
|
throw new error_1.DBOSConflictingWorkflowError(initStatus.workflowUUID, msg);
|
|
@@ -590,31 +588,12 @@ class SystemDatabase {
|
|
|
590
588
|
}
|
|
591
589
|
const status = resRow.status;
|
|
592
590
|
const deadlineEpochMS = resRow.workflow_deadline_epoch_ms ?? undefined;
|
|
593
|
-
// If there is an existing DB record and we aren't here to recover it,
|
|
594
|
-
|
|
595
|
-
if (ownerXid !== resRow.owner_xid && !options?.isRecoveryRequest && !options?.isDequeuedRequest) {
|
|
596
|
-
// It is not clear if getting the handle should throw the error, or getting the result from the handle should error.
|
|
597
|
-
// Current precedent is the former.
|
|
598
|
-
if (status === workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED) {
|
|
599
|
-
throw new error_1.DBOSMaxRecoveryAttemptsExceededError(initStatus.workflowUUID, options?.maxRetries ?? -1);
|
|
600
|
-
}
|
|
591
|
+
// If there is an existing DB record and we aren't here to recover it, leave it be.
|
|
592
|
+
if (ownerXid !== resRow.owner_xid) {
|
|
601
593
|
return { status, deadlineEpochMS, shouldExecuteOnThisExecutor: false, serialization: resRow.serialization };
|
|
602
594
|
}
|
|
603
|
-
// Upsert above already set executor assignment
|
|
595
|
+
// Upsert above already set executor assignment
|
|
604
596
|
shouldCommit = true;
|
|
605
|
-
// recovery_attempt means "attempts" (we kept the name for backward compatibility). It's default value is 1.
|
|
606
|
-
// Every time we init the status, we increment `recovery_attempts` by 1.
|
|
607
|
-
// Thus, when this number becomes equal to `maxRetries + 1`, we should mark the workflow as `MAX_RECOVERY_ATTEMPTS_EXCEEDED`.
|
|
608
|
-
const attempts = resRow.recovery_attempts;
|
|
609
|
-
if (options?.maxRetries && attempts > options?.maxRetries + 1) {
|
|
610
|
-
await this.updateWorkflowStatus(client, initStatus.workflowUUID, workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED, {
|
|
611
|
-
where: { status: workflow_1.StatusString.PENDING },
|
|
612
|
-
throwOnFailure: false,
|
|
613
|
-
update: { resetDeduplicationID: true },
|
|
614
|
-
});
|
|
615
|
-
throw new error_1.DBOSMaxRecoveryAttemptsExceededError(initStatus.workflowUUID, options.maxRetries);
|
|
616
|
-
}
|
|
617
|
-
this.logger.debug(`Workflow ${initStatus.workflowUUID} attempt number: ${attempts}.`);
|
|
618
597
|
return {
|
|
619
598
|
status,
|
|
620
599
|
deadlineEpochMS,
|
|
@@ -637,6 +616,19 @@ class SystemDatabase {
|
|
|
637
616
|
}
|
|
638
617
|
}
|
|
639
618
|
}
|
|
619
|
+
/** Move claimed workflows that exhausted their attempts off the queue, leaving rows others have moved on alone. */
|
|
620
|
+
async deadLetterWorkflows(workflowIDs, minRecoveryAttempts) {
|
|
621
|
+
if (workflowIDs.length === 0)
|
|
622
|
+
return;
|
|
623
|
+
await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
624
|
+
SET status = $1,
|
|
625
|
+
deduplication_id = NULL,
|
|
626
|
+
started_at_epoch_ms = NULL,
|
|
627
|
+
queue_name = NULL,
|
|
628
|
+
updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
|
|
629
|
+
completed_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
630
|
+
WHERE workflow_uuid = ANY($2::text[]) AND status = $3 AND recovery_attempts >= $4`, [workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED, workflowIDs, workflow_1.StatusString.PENDING, minRecoveryAttempts]);
|
|
631
|
+
}
|
|
640
632
|
/** Highest created_at among still-active rows per partition key, used to seed the in-memory cursor. */
|
|
641
633
|
async #maxPartitionKeyCreatedAt(keys) {
|
|
642
634
|
const maxima = new Map();
|
|
@@ -832,24 +824,17 @@ class SystemDatabase {
|
|
|
832
824
|
}
|
|
833
825
|
// Recovery re-enqueues rather than executing directly so the queue's atomic dequeue admits exactly one runner, and the executor ID predicate rejects sweeps for rows a live executor has already claimed.
|
|
834
826
|
async reenqueueWorkflowsForRecovery(executorID, appVersion, recoveryQueueName) {
|
|
835
|
-
const params = [
|
|
836
|
-
workflow_1.StatusString.ENQUEUED,
|
|
837
|
-
Date.now(),
|
|
838
|
-
recoveryQueueName,
|
|
839
|
-
workflow_1.StatusString.PENDING,
|
|
840
|
-
executorID,
|
|
841
|
-
appVersion,
|
|
842
|
-
];
|
|
827
|
+
const params = [workflow_1.StatusString.ENQUEUED, recoveryQueueName, workflow_1.StatusString.PENDING, executorID, appVersion];
|
|
843
828
|
// executor_id defaults to "local", so it collides across applications.
|
|
844
829
|
const scope = this.#appNameFilter('application_name', this.appName, params);
|
|
845
830
|
const result = await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
846
831
|
SET started_at_epoch_ms = NULL,
|
|
847
832
|
status = $1,
|
|
848
|
-
updated_at =
|
|
849
|
-
queue_name = COALESCE(queue_name, $
|
|
850
|
-
WHERE status = $
|
|
851
|
-
AND executor_id = $
|
|
852
|
-
AND application_version = $
|
|
833
|
+
updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
|
|
834
|
+
queue_name = COALESCE(queue_name, $2)
|
|
835
|
+
WHERE status = $3
|
|
836
|
+
AND executor_id = $4
|
|
837
|
+
AND application_version = $5
|
|
853
838
|
AND ${scope}
|
|
854
839
|
RETURNING workflow_uuid`, params);
|
|
855
840
|
return result.rows.map((row) => row.workflow_uuid);
|
|
@@ -879,12 +864,33 @@ class SystemDatabase {
|
|
|
879
864
|
return json ? JSON.parse(json) : null;
|
|
880
865
|
}
|
|
881
866
|
}
|
|
867
|
+
/** Max IDs per {@link getWorkflowStatuses} fetch: listWorkflows binds one parameter per ID. */
|
|
868
|
+
statusFetchChunkSize = 500;
|
|
869
|
+
// Retried per chunk so a reconnect refetches one chunk, not every chunk before it.
|
|
870
|
+
async fetchWorkflowStatusChunk(workflowIDs) {
|
|
871
|
+
return await this.listWorkflows({ workflowIDs, loadInput: true, loadOutput: false });
|
|
872
|
+
}
|
|
873
|
+
/** Fetch many statuses in as few round trips as possible. IDs with no row are omitted. */
|
|
874
|
+
async getWorkflowStatuses(workflowIDs) {
|
|
875
|
+
const statuses = new Map();
|
|
876
|
+
for (let start = 0; start < workflowIDs.length; start += this.statusFetchChunkSize) {
|
|
877
|
+
for (const status of await this.fetchWorkflowStatusChunk(workflowIDs.slice(start, start + this.statusFetchChunkSize))) {
|
|
878
|
+
statuses.set(status.workflowUUID, status);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return statuses;
|
|
882
|
+
}
|
|
882
883
|
// Only used in tests
|
|
883
884
|
async setWorkflowStatus(workflowID, status, resetRecoveryAttempts, internalOptions) {
|
|
884
885
|
const client = await this.pool.connect();
|
|
885
886
|
try {
|
|
886
887
|
await this.updateWorkflowStatus(client, workflowID, status, {
|
|
887
|
-
update: {
|
|
888
|
+
update: {
|
|
889
|
+
resetRecoveryAttempts,
|
|
890
|
+
resetNameTo: internalOptions?.updateName,
|
|
891
|
+
queueName: internalOptions?.queueName,
|
|
892
|
+
resetStartedAtEpochMs: internalOptions?.resetStartedAtEpochMs,
|
|
893
|
+
},
|
|
888
894
|
});
|
|
889
895
|
}
|
|
890
896
|
finally {
|
|
@@ -1023,15 +1029,15 @@ class SystemDatabase {
|
|
|
1023
1029
|
}
|
|
1024
1030
|
async setWorkflowPriority(workflowID, priority) {
|
|
1025
1031
|
await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
1026
|
-
SET priority = $1, updated_at =
|
|
1027
|
-
WHERE workflow_uuid = $
|
|
1028
|
-
AND status IN ($
|
|
1032
|
+
SET priority = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
1033
|
+
WHERE workflow_uuid = $2
|
|
1034
|
+
AND status IN ($3, $4)`, [priority, workflowID, workflow_1.StatusString.ENQUEUED, workflow_1.StatusString.DELAYED]);
|
|
1029
1035
|
}
|
|
1030
1036
|
async setWorkflowDelay(workflowID, delayUntilEpochMS) {
|
|
1031
1037
|
await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
1032
|
-
SET delay_until_epoch_ms = $1, updated_at =
|
|
1033
|
-
WHERE workflow_uuid = $
|
|
1034
|
-
AND status = $
|
|
1038
|
+
SET delay_until_epoch_ms = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
1039
|
+
WHERE workflow_uuid = $2
|
|
1040
|
+
AND status = $3`, [delayUntilEpochMS, workflowID, workflow_1.StatusString.DELAYED]);
|
|
1035
1041
|
}
|
|
1036
1042
|
/**
|
|
1037
1043
|
* Extend an existing debounced DELAYED workflow's delay and update its inputs, atomically.
|
|
@@ -1071,7 +1077,6 @@ class SystemDatabase {
|
|
|
1071
1077
|
params.delayUntilEpochMS,
|
|
1072
1078
|
params.input,
|
|
1073
1079
|
params.serialization,
|
|
1074
|
-
Date.now(),
|
|
1075
1080
|
params.workflowName,
|
|
1076
1081
|
classNameOrNull,
|
|
1077
1082
|
params.queueName,
|
|
@@ -1087,12 +1092,13 @@ class SystemDatabase {
|
|
|
1087
1092
|
THEN debounce_deadline_epoch_ms
|
|
1088
1093
|
ELSE $1
|
|
1089
1094
|
END,
|
|
1090
|
-
inputs = $2, serialization = $3,
|
|
1095
|
+
inputs = $2, serialization = $3,
|
|
1096
|
+
updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
|
|
1091
1097
|
-- Claim it for the target, as its dequeue would: left unclaimed, every peer coalesces onto the one workflow and the last inputs win.
|
|
1092
|
-
application_name = COALESCE(application_name, $
|
|
1093
|
-
WHERE name = $
|
|
1094
|
-
AND queue_name = $
|
|
1095
|
-
AND status = $
|
|
1098
|
+
application_name = COALESCE(application_name, $9)
|
|
1099
|
+
WHERE name = $4 AND class_name IS NOT DISTINCT FROM $5
|
|
1100
|
+
AND queue_name = $6 AND deduplication_id = $7
|
|
1101
|
+
AND status = $8 AND is_debounced = TRUE
|
|
1096
1102
|
AND ${ownScope}
|
|
1097
1103
|
RETURNING workflow_uuid`, updateParams);
|
|
1098
1104
|
if (updated.rows.length > 0) {
|
|
@@ -2216,7 +2222,7 @@ class SystemDatabase {
|
|
|
2216
2222
|
// Only what this application would dequeue: a peer's debounce key is not ours to clear.
|
|
2217
2223
|
const scope = this.#appNameFilter('application_name', this.appName, params);
|
|
2218
2224
|
await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
2219
|
-
SET status = $1, updated_at =
|
|
2225
|
+
SET status = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
|
|
2220
2226
|
deduplication_id = CASE WHEN is_debounced THEN NULL ELSE deduplication_id END
|
|
2221
2227
|
WHERE status = $3 AND delay_until_epoch_ms <= $2 AND ${scope}`, params);
|
|
2222
2228
|
}
|
|
@@ -2248,7 +2254,6 @@ class SystemDatabase {
|
|
|
2248
2254
|
return rows.map((row) => row.pk);
|
|
2249
2255
|
}
|
|
2250
2256
|
async findAndMarkStartableWorkflows(queue, executorID, appVersion, queuePartitionKey) {
|
|
2251
|
-
const startTimeMs = Date.now();
|
|
2252
2257
|
const limiterPeriodMS = queue.rateLimit ? queue.rateLimit.periodSec * 1000 : 0;
|
|
2253
2258
|
const claimedIDs = [];
|
|
2254
2259
|
const localRunningForQueue = this.countRunningWorkflowsForQueue(queue.name, queuePartitionKey);
|
|
@@ -2275,7 +2280,7 @@ class SystemDatabase {
|
|
|
2275
2280
|
queue.name,
|
|
2276
2281
|
workflow_1.StatusString.ENQUEUED,
|
|
2277
2282
|
workflow_1.StatusString.DELAYED,
|
|
2278
|
-
|
|
2283
|
+
limiterPeriodMS,
|
|
2279
2284
|
...partitionParams,
|
|
2280
2285
|
];
|
|
2281
2286
|
// Count only what this application would dequeue, matching the select below.
|
|
@@ -2284,7 +2289,8 @@ class SystemDatabase {
|
|
|
2284
2289
|
WHERE queue_name = $1
|
|
2285
2290
|
AND rate_limited = TRUE
|
|
2286
2291
|
AND status NOT IN ($2, $3)
|
|
2287
|
-
|
|
2292
|
+
-- Database clock on both sides, as the claim stamps started_at_epoch_ms with it.
|
|
2293
|
+
AND started_at_epoch_ms > (EXTRACT(epoch FROM now()) * 1000)::bigint - $4
|
|
2288
2294
|
AND ${scope}
|
|
2289
2295
|
${partitionFilter.replace('$PARTITION', '$5')}`, params);
|
|
2290
2296
|
numRecentQueries = Number(countResult.rows[0].count);
|
|
@@ -2297,9 +2303,13 @@ class SystemDatabase {
|
|
|
2297
2303
|
// If there is a global or local concurrency limit N, select only the N oldest enqueued
|
|
2298
2304
|
// functions, else select all of them.
|
|
2299
2305
|
let maxTasks = Infinity;
|
|
2306
|
+
if (queue.rateLimit) {
|
|
2307
|
+
// Bound the claim by the limiter's remaining slots so a backlogged queue locks only what it can start.
|
|
2308
|
+
maxTasks = Math.max(0, queue.rateLimit.limitPerPeriod - numRecentQueries);
|
|
2309
|
+
}
|
|
2300
2310
|
if (queue.workerConcurrency !== undefined) {
|
|
2301
2311
|
// Use the in-memory registry for this worker's running count — avoids a DB round trip.
|
|
2302
|
-
maxTasks = Math.max(0, queue.workerConcurrency - localRunningForQueue);
|
|
2312
|
+
maxTasks = Math.min(maxTasks, Math.max(0, queue.workerConcurrency - localRunningForQueue));
|
|
2303
2313
|
}
|
|
2304
2314
|
if (queue.concurrency !== undefined) {
|
|
2305
2315
|
// Global concurrency still requires a DB query since other workers may be running workflows too.
|
|
@@ -2327,7 +2337,10 @@ class SystemDatabase {
|
|
|
2327
2337
|
const versionClause = isLatestVersion
|
|
2328
2338
|
? '(application_version = $3 OR application_version IS NULL)'
|
|
2329
2339
|
: 'application_version = $3';
|
|
2330
|
-
|
|
2340
|
+
// A limit shared across processes needs a consistent view of the table: NOWAIT makes an
|
|
2341
|
+
// overlapping dequeuer abort rather than claim the next rows and spend the same budget twice.
|
|
2342
|
+
const sharedBudget = queue.concurrency !== undefined || queue.rateLimit !== undefined;
|
|
2343
|
+
const lockMode = sharedBudget ? 'FOR UPDATE NOWAIT' : 'FOR UPDATE SKIP LOCKED';
|
|
2331
2344
|
const limitClause = maxTasks !== Infinity ? `LIMIT ${maxTasks}` : '';
|
|
2332
2345
|
const selectParams = [workflow_1.StatusString.ENQUEUED, queue.name, appVersion, ...partitionParams];
|
|
2333
2346
|
const selectScope = this.#appNameFilter('application_name', this.appName, selectParams);
|
|
@@ -2349,43 +2362,41 @@ class SystemDatabase {
|
|
|
2349
2362
|
await (0, debugpoint_1.debugTriggerPoint)(debugpoint_1.DEBUG_TRIGGER_FIND_AND_MARK_AFTER_SELECT);
|
|
2350
2363
|
// Start the workflows
|
|
2351
2364
|
const workflowIDs = rows.map((row) => row.workflow_uuid);
|
|
2352
|
-
|
|
2353
|
-
// If we have a rate limit, stop starting functions when the number
|
|
2354
|
-
// of functions started this period exceeds the limit.
|
|
2355
|
-
if (queue.rateLimit && claimedIDs.length + numRecentQueries >= queue.rateLimit.limitPerPeriod) {
|
|
2356
|
-
break;
|
|
2357
|
-
}
|
|
2365
|
+
if (workflowIDs.length > 0) {
|
|
2358
2366
|
// Start the functions by marking them as pending and updating their executor IDs.
|
|
2359
|
-
// Only claim the workflow if the UPDATE actually transitioned an ENQUEUED row —
|
|
2360
|
-
// otherwise another worker won the race and we must not re-dispatch it.
|
|
2361
2367
|
const updateParams = [
|
|
2362
2368
|
workflow_1.StatusString.PENDING,
|
|
2363
2369
|
executorID,
|
|
2364
2370
|
appVersion,
|
|
2365
|
-
startTimeMs,
|
|
2366
2371
|
queue.rateLimit !== undefined,
|
|
2367
|
-
|
|
2372
|
+
workflowIDs,
|
|
2368
2373
|
workflow_1.StatusString.ENQUEUED,
|
|
2369
2374
|
// Claim an unclaimed row for this application; a nameless dequeuer leaves ownership untouched.
|
|
2370
2375
|
this.appName ?? null,
|
|
2371
2376
|
];
|
|
2372
2377
|
// Re-check ownership alongside status, as the partitioned claim guard does.
|
|
2373
2378
|
const claimScope = this.#appNameFilter('application_name', this.appName, updateParams);
|
|
2374
|
-
|
|
2379
|
+
// RETURNING reports exactly the rows this statement flipped, so a row another worker won is absent.
|
|
2380
|
+
const flippedResult = await client.query(`UPDATE "${this.schemaName}".workflow_status
|
|
2375
2381
|
SET status = $1,
|
|
2376
2382
|
executor_id = $2,
|
|
2377
2383
|
application_version = $3,
|
|
2378
|
-
started_at_epoch_ms =
|
|
2379
|
-
rate_limited = $
|
|
2380
|
-
application_name = COALESCE(application_name, $
|
|
2384
|
+
started_at_epoch_ms = (EXTRACT(epoch FROM now()) * 1000)::bigint,
|
|
2385
|
+
rate_limited = $4,
|
|
2386
|
+
application_name = COALESCE(application_name, $7),
|
|
2387
|
+
recovery_attempts = recovery_attempts + 1,
|
|
2388
|
+
updated_at = (EXTRACT(epoch FROM now()) * 1000)::bigint,
|
|
2381
2389
|
workflow_deadline_epoch_ms = CASE
|
|
2382
2390
|
WHEN workflow_timeout_ms IS NOT NULL AND workflow_deadline_epoch_ms IS NULL
|
|
2383
2391
|
THEN (EXTRACT(epoch FROM now()) * 1000)::bigint + workflow_timeout_ms
|
|
2384
2392
|
ELSE workflow_deadline_epoch_ms
|
|
2385
2393
|
END
|
|
2386
|
-
WHERE workflow_uuid = $
|
|
2387
|
-
|
|
2388
|
-
|
|
2394
|
+
WHERE workflow_uuid = ANY($5::text[]) AND status = $6 AND ${claimScope}
|
|
2395
|
+
RETURNING workflow_uuid`, updateParams);
|
|
2396
|
+
const flippedIDs = new Set(flippedResult.rows.map((row) => row.workflow_uuid));
|
|
2397
|
+
for (const id of workflowIDs) {
|
|
2398
|
+
if (flippedIDs.has(id))
|
|
2399
|
+
claimedIDs.push(id);
|
|
2389
2400
|
}
|
|
2390
2401
|
}
|
|
2391
2402
|
await client.query('COMMIT');
|
|
@@ -2408,7 +2419,6 @@ class SystemDatabase {
|
|
|
2408
2419
|
throw new error_1.DBOSError(`Batched partitioned dequeue requires a queue with concurrency 1 and no rate limit: ${queue.name}`);
|
|
2409
2420
|
}
|
|
2410
2421
|
// workerConcurrency needs no handling here: dispatch routes 0 (the pause-dequeue idiom) to the fallback path, and validation caps any other value at concurrency=1, which the PENDING gate already enforces globally.
|
|
2411
|
-
const startTimeMs = Date.now();
|
|
2412
2422
|
const client = await this.pool.connect();
|
|
2413
2423
|
try {
|
|
2414
2424
|
await client.query('BEGIN');
|
|
@@ -2495,7 +2505,6 @@ class SystemDatabase {
|
|
|
2495
2505
|
workflow_1.StatusString.ENQUEUED,
|
|
2496
2506
|
queue.name,
|
|
2497
2507
|
appVersion,
|
|
2498
|
-
startTimeMs,
|
|
2499
2508
|
// Claim the row, as the unpartitioned dequeue does.
|
|
2500
2509
|
this.appName ?? null,
|
|
2501
2510
|
];
|
|
@@ -2505,9 +2514,11 @@ class SystemDatabase {
|
|
|
2505
2514
|
SET status = $1,
|
|
2506
2515
|
executor_id = $2,
|
|
2507
2516
|
application_version = $6,
|
|
2508
|
-
started_at_epoch_ms =
|
|
2517
|
+
started_at_epoch_ms = (EXTRACT(epoch FROM now()) * 1000)::bigint,
|
|
2509
2518
|
rate_limited = FALSE,
|
|
2510
|
-
application_name = COALESCE(application_name, $
|
|
2519
|
+
application_name = COALESCE(application_name, $7),
|
|
2520
|
+
recovery_attempts = recovery_attempts + 1,
|
|
2521
|
+
updated_at = (EXTRACT(epoch FROM now()) * 1000)::bigint,
|
|
2511
2522
|
workflow_deadline_epoch_ms = CASE
|
|
2512
2523
|
WHEN workflow_timeout_ms IS NOT NULL AND workflow_deadline_epoch_ms IS NULL
|
|
2513
2524
|
THEN (EXTRACT(epoch FROM now()) * 1000)::bigint + workflow_timeout_ms
|
|
@@ -3550,7 +3561,7 @@ class SystemDatabase {
|
|
|
3550
3561
|
return { queues, schedules, versions, workflows: inFlight + terminal, steps };
|
|
3551
3562
|
}
|
|
3552
3563
|
// ==================== Internal ====================
|
|
3553
|
-
async insertWorkflowStatus(client, initStatus, ownerXid
|
|
3564
|
+
async insertWorkflowStatus(client, initStatus, ownerXid) {
|
|
3554
3565
|
try {
|
|
3555
3566
|
const { rows } = await client.query(`INSERT INTO "${this.schemaName}".workflow_status (
|
|
3556
3567
|
workflow_uuid,
|
|
@@ -3566,9 +3577,7 @@ class SystemDatabase {
|
|
|
3566
3577
|
executor_id,
|
|
3567
3578
|
application_version,
|
|
3568
3579
|
application_id,
|
|
3569
|
-
created_at,
|
|
3570
3580
|
recovery_attempts,
|
|
3571
|
-
updated_at,
|
|
3572
3581
|
workflow_timeout_ms,
|
|
3573
3582
|
workflow_deadline_epoch_ms,
|
|
3574
3583
|
inputs,
|
|
@@ -3585,21 +3594,16 @@ class SystemDatabase {
|
|
|
3585
3594
|
debounce_deadline_epoch_ms,
|
|
3586
3595
|
is_debounced,
|
|
3587
3596
|
application_name
|
|
3588
|
-
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $26, $27, $28, $29, $30
|
|
3597
|
+
) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)
|
|
3589
3598
|
ON CONFLICT (workflow_uuid)
|
|
3590
3599
|
DO UPDATE SET
|
|
3591
|
-
|
|
3592
|
-
WHEN workflow_status.status != '${workflow_1.StatusString.ENQUEUED}' AND workflow_status.status != '${workflow_1.StatusString.DELAYED}'
|
|
3593
|
-
THEN workflow_status.recovery_attempts + $25
|
|
3594
|
-
ELSE workflow_status.recovery_attempts
|
|
3595
|
-
END,
|
|
3596
|
-
updated_at = EXCLUDED.updated_at,
|
|
3600
|
+
updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
|
|
3597
3601
|
executor_id = CASE
|
|
3598
3602
|
WHEN EXCLUDED.status != '${workflow_1.StatusString.ENQUEUED}' AND EXCLUDED.status != '${workflow_1.StatusString.DELAYED}'
|
|
3599
3603
|
THEN EXCLUDED.executor_id
|
|
3600
3604
|
ELSE workflow_status.executor_id
|
|
3601
3605
|
END
|
|
3602
|
-
RETURNING
|
|
3606
|
+
RETURNING status, name, class_name, config_name, queue_name, workflow_deadline_epoch_ms, executor_id, owner_xid, serialization`, [
|
|
3603
3607
|
initStatus.workflowUUID,
|
|
3604
3608
|
initStatus.status,
|
|
3605
3609
|
initStatus.workflowName,
|
|
@@ -3614,9 +3618,7 @@ class SystemDatabase {
|
|
|
3614
3618
|
initStatus.executorId,
|
|
3615
3619
|
initStatus.applicationVersion ?? null,
|
|
3616
3620
|
initStatus.applicationID,
|
|
3617
|
-
initStatus.createdAt,
|
|
3618
3621
|
initStatus.status === workflow_1.StatusString.ENQUEUED || initStatus.status === workflow_1.StatusString.DELAYED ? 0 : 1,
|
|
3619
|
-
initStatus.updatedAt ?? Date.now(),
|
|
3620
3622
|
initStatus.timeoutMS ?? null,
|
|
3621
3623
|
initStatus.deadlineEpochMS ?? null,
|
|
3622
3624
|
initStatus.input ?? null,
|
|
@@ -3625,7 +3627,6 @@ class SystemDatabase {
|
|
|
3625
3627
|
initStatus.queuePartitionKey ?? null,
|
|
3626
3628
|
initStatus.forkedFrom ?? null,
|
|
3627
3629
|
initStatus.parentWorkflowID ?? null,
|
|
3628
|
-
(incrementAttempts ?? false) ? 1 : 0,
|
|
3629
3630
|
initStatus.serialization,
|
|
3630
3631
|
ownerXid,
|
|
3631
3632
|
initStatus.delayUntilEpochMS ?? null,
|
|
@@ -3957,9 +3958,15 @@ exports.SystemDatabase = SystemDatabase;
|
|
|
3957
3958
|
__decorate([
|
|
3958
3959
|
dbRetry(),
|
|
3959
3960
|
__metadata("design:type", Function),
|
|
3960
|
-
__metadata("design:paramtypes", [Object, Object
|
|
3961
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
3961
3962
|
__metadata("design:returntype", Promise)
|
|
3962
3963
|
], SystemDatabase.prototype, "initWorkflowStatus", null);
|
|
3964
|
+
__decorate([
|
|
3965
|
+
dbRetry(),
|
|
3966
|
+
__metadata("design:type", Function),
|
|
3967
|
+
__metadata("design:paramtypes", [Array, Number]),
|
|
3968
|
+
__metadata("design:returntype", Promise)
|
|
3969
|
+
], SystemDatabase.prototype, "deadLetterWorkflows", null);
|
|
3963
3970
|
__decorate([
|
|
3964
3971
|
dbRetry(),
|
|
3965
3972
|
__metadata("design:type", Function),
|
|
@@ -3978,6 +3985,12 @@ __decorate([
|
|
|
3978
3985
|
__metadata("design:paramtypes", [String, String, Number]),
|
|
3979
3986
|
__metadata("design:returntype", Promise)
|
|
3980
3987
|
], SystemDatabase.prototype, "getWorkflowStatus", null);
|
|
3988
|
+
__decorate([
|
|
3989
|
+
dbRetry(),
|
|
3990
|
+
__metadata("design:type", Function),
|
|
3991
|
+
__metadata("design:paramtypes", [Array]),
|
|
3992
|
+
__metadata("design:returntype", Promise)
|
|
3993
|
+
], SystemDatabase.prototype, "fetchWorkflowStatusChunk", null);
|
|
3981
3994
|
__decorate([
|
|
3982
3995
|
dbRetry(),
|
|
3983
3996
|
__metadata("design:type", Function),
|