@dbos-inc/dbos-sdk 4.20.3-preview → 4.20.7-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 +4 -3
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +17 -5
- package/dist/src/client.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +1 -1
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +2 -2
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos.d.ts +44 -6
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +46 -10
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/system_database.d.ts +5 -6
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js +138 -226
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/workflow.d.ts +5 -4
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +7 -3
- package/dist/src/workflow.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -385,10 +385,8 @@ class SystemDatabase {
|
|
|
385
385
|
notificationsMap = new NotificationMap();
|
|
386
386
|
workflowEventsMap = new NotificationMap();
|
|
387
387
|
streamsMap = new NotificationMap();
|
|
388
|
-
cancelWakeupMap = new NotificationMap();
|
|
389
388
|
customPool = false;
|
|
390
389
|
runningWorkflowMap = new Map(); // Map from workflowID to workflow promise, queue name and partition key
|
|
391
|
-
workflowCancellationMap = new Map(); // Map from workflowID to its cancellation status.
|
|
392
390
|
constructor(systemDatabaseUrl, logger, serializer, sysDbPoolSize = exports.DEFAULT_POOL_SIZE, systemDatabasePool, schemaName = 'dbos', useListenNotify = true) {
|
|
393
391
|
this.systemDatabaseUrl = systemDatabaseUrl;
|
|
394
392
|
this.logger = logger;
|
|
@@ -517,9 +515,7 @@ class SystemDatabase {
|
|
|
517
515
|
async recordWorkflowOutput(workflowID, status) {
|
|
518
516
|
const client = await this.pool.connect();
|
|
519
517
|
try {
|
|
520
|
-
await this
|
|
521
|
-
update: { output: status.output, resetDeduplicationID: true, setCompletedAt: true },
|
|
522
|
-
});
|
|
518
|
+
await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.SUCCESS, { output: status.output });
|
|
523
519
|
}
|
|
524
520
|
finally {
|
|
525
521
|
client.release();
|
|
@@ -528,14 +524,37 @@ class SystemDatabase {
|
|
|
528
524
|
async recordWorkflowError(workflowID, status) {
|
|
529
525
|
const client = await this.pool.connect();
|
|
530
526
|
try {
|
|
531
|
-
await this
|
|
532
|
-
update: { error: status.error, resetDeduplicationID: true, setCompletedAt: true },
|
|
533
|
-
});
|
|
527
|
+
await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.ERROR, { error: status.error });
|
|
534
528
|
}
|
|
535
529
|
finally {
|
|
536
530
|
client.release();
|
|
537
531
|
}
|
|
538
532
|
}
|
|
533
|
+
// Record a workflow's terminal outcome (SUCCESS or ERROR), but never overwrite
|
|
534
|
+
// the terminal CANCELLED status: a workflow can be cancelled during its final
|
|
535
|
+
// step, and if so it must not be able to subsequently complete. If the
|
|
536
|
+
// workflow is cancelled, abort the function so it does not complete. This
|
|
537
|
+
// mirrors the cancellation check done before each step.
|
|
538
|
+
async #recordWorkflowOutcome(client, workflowID, status, outcome) {
|
|
539
|
+
let cancelled = false;
|
|
540
|
+
try {
|
|
541
|
+
await client.query('BEGIN');
|
|
542
|
+
await this.updateWorkflowStatus(client, workflowID, status, {
|
|
543
|
+
update: { ...outcome, resetDeduplicationID: true, setCompletedAt: true },
|
|
544
|
+
where: { notStatus: workflow_1.StatusString.CANCELLED },
|
|
545
|
+
throwOnFailure: false,
|
|
546
|
+
});
|
|
547
|
+
cancelled = (await this.getWorkflowStatusValue(client, workflowID)) === workflow_1.StatusString.CANCELLED;
|
|
548
|
+
await client.query('COMMIT');
|
|
549
|
+
}
|
|
550
|
+
catch (e) {
|
|
551
|
+
await client.query('ROLLBACK');
|
|
552
|
+
throw e;
|
|
553
|
+
}
|
|
554
|
+
if (cancelled) {
|
|
555
|
+
throw new error_1.DBOSWorkflowCancelledError(workflowID);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
539
558
|
async getPendingWorkflows(executorID, appVersion) {
|
|
540
559
|
const getWorkflows = await this.pool.query(`SELECT workflow_uuid, queue_name
|
|
541
560
|
FROM "${this.schemaName}".workflow_status
|
|
@@ -697,23 +716,11 @@ class SystemDatabase {
|
|
|
697
716
|
completed_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
698
717
|
WHERE workflow_uuid = ANY($2)
|
|
699
718
|
AND status NOT IN ($3, $4)`, [workflow_1.StatusString.CANCELLED, workflowIDs, workflow_1.StatusString.SUCCESS, workflow_1.StatusString.ERROR]);
|
|
700
|
-
for (const workflowID of workflowIDs) {
|
|
701
|
-
this.#setWFCancelMap(workflowID);
|
|
702
|
-
}
|
|
703
719
|
}
|
|
704
720
|
async checkIfCanceled(workflowID) {
|
|
705
|
-
|
|
706
|
-
try {
|
|
707
|
-
await this.#checkIfCanceled(client, workflowID);
|
|
708
|
-
}
|
|
709
|
-
finally {
|
|
710
|
-
client.release();
|
|
711
|
-
}
|
|
721
|
+
await this.#checkIfCanceled(this.pool, workflowID);
|
|
712
722
|
}
|
|
713
723
|
async resumeWorkflows(workflowIDs, queueName) {
|
|
714
|
-
for (const workflowID of workflowIDs) {
|
|
715
|
-
this.#clearWFCancelMap(workflowID);
|
|
716
|
-
}
|
|
717
724
|
await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
|
|
718
725
|
SET status = $1, queue_name = $2, recovery_attempts = 0,
|
|
719
726
|
workflow_deadline_epoch_ms = NULL, deduplication_id = NULL,
|
|
@@ -768,7 +775,6 @@ class SystemDatabase {
|
|
|
768
775
|
await this.pool.query(`DELETE FROM "${this.schemaName}".workflow_status WHERE workflow_uuid = ANY($1)`, [allIds]);
|
|
769
776
|
for (const wfid of allIds) {
|
|
770
777
|
this.runningWorkflowMap.delete(wfid);
|
|
771
|
-
this.workflowCancellationMap.delete(wfid);
|
|
772
778
|
}
|
|
773
779
|
}
|
|
774
780
|
async forkWorkflow(workflowID, startStep, options = {}) {
|
|
@@ -1131,7 +1137,6 @@ class SystemDatabase {
|
|
|
1131
1137
|
.finally(() => {
|
|
1132
1138
|
// Remove itself from pending workflow map.
|
|
1133
1139
|
this.runningWorkflowMap.delete(workflowID);
|
|
1134
|
-
this.workflowCancellationMap.delete(workflowID);
|
|
1135
1140
|
});
|
|
1136
1141
|
this.runningWorkflowMap.set(workflowID, {
|
|
1137
1142
|
promise: awaitWorkflowPromise,
|
|
@@ -1164,149 +1169,91 @@ class SystemDatabase {
|
|
|
1164
1169
|
//throw new Error('Message notification map is not empty - shutdown is not clean.');
|
|
1165
1170
|
}
|
|
1166
1171
|
}
|
|
1167
|
-
async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID) {
|
|
1172
|
+
async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID, pollingIntervalMs) {
|
|
1168
1173
|
const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
|
|
1169
1174
|
let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
|
|
1175
|
+
const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
|
|
1170
1176
|
while (true) {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
resolveNotification = resolve;
|
|
1174
|
-
});
|
|
1175
|
-
const irh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
|
|
1176
|
-
resolveNotification();
|
|
1177
|
-
});
|
|
1178
|
-
const crh = callerID
|
|
1179
|
-
? this.cancelWakeupMap.registerCallback(callerID, (_res) => {
|
|
1180
|
-
resolveNotification();
|
|
1181
|
-
})
|
|
1182
|
-
: undefined;
|
|
1177
|
+
if (callerID)
|
|
1178
|
+
await this.checkIfCanceled(callerID);
|
|
1183
1179
|
try {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1203
|
-
else {
|
|
1204
|
-
// Status is not actionable
|
|
1205
|
-
}
|
|
1180
|
+
const { rows } = await this.pool.query(`SELECT status, output, error, serialization FROM "${this.schemaName}".workflow_status
|
|
1181
|
+
WHERE workflow_uuid=$1`, [workflowID]);
|
|
1182
|
+
if (rows.length > 0) {
|
|
1183
|
+
const status = rows[0].status;
|
|
1184
|
+
if (status === workflow_1.StatusString.SUCCESS) {
|
|
1185
|
+
return { output: rows[0].output, serialization: rows[0].serialization };
|
|
1186
|
+
}
|
|
1187
|
+
else if (status === workflow_1.StatusString.ERROR) {
|
|
1188
|
+
return { error: rows[0].error, serialization: rows[0].serialization };
|
|
1189
|
+
}
|
|
1190
|
+
else if (status === workflow_1.StatusString.CANCELLED) {
|
|
1191
|
+
return { cancelled: true };
|
|
1192
|
+
}
|
|
1193
|
+
else if (status === workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED) {
|
|
1194
|
+
return { maxRecoveryAttemptsExceeded: true };
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
// Status is not actionable
|
|
1206
1198
|
}
|
|
1207
|
-
}
|
|
1208
|
-
catch (e) {
|
|
1209
|
-
const err = e;
|
|
1210
|
-
this.logger.error(`Exception from system database: ${err}`, err);
|
|
1211
|
-
throw err;
|
|
1212
|
-
}
|
|
1213
|
-
const ct = Date.now();
|
|
1214
|
-
if (finishTime && ct > finishTime)
|
|
1215
|
-
return undefined; // Time's up
|
|
1216
|
-
let timeoutPromise = Promise.resolve();
|
|
1217
|
-
let timeoutCancel = () => { };
|
|
1218
|
-
if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
|
|
1219
|
-
const { promise, cancel, endTime } = await this.#durableSleep(callerID, timerFuncID, timeoutms, this.dbPollingIntervalResultMs);
|
|
1220
|
-
finishTime = endTime;
|
|
1221
|
-
timeoutPromise = promise;
|
|
1222
|
-
timeoutCancel = cancel;
|
|
1223
|
-
}
|
|
1224
|
-
else {
|
|
1225
|
-
let poll = finishTime ? finishTime - ct : this.dbPollingIntervalResultMs;
|
|
1226
|
-
poll = Math.min(this.dbPollingIntervalResultMs, poll);
|
|
1227
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
|
1228
|
-
timeoutPromise = promise;
|
|
1229
|
-
timeoutCancel = cancel;
|
|
1230
|
-
}
|
|
1231
|
-
try {
|
|
1232
|
-
await Promise.race([statusPromise, timeoutPromise]);
|
|
1233
|
-
}
|
|
1234
|
-
finally {
|
|
1235
|
-
timeoutCancel();
|
|
1236
1199
|
}
|
|
1237
1200
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1201
|
+
catch (e) {
|
|
1202
|
+
const err = e;
|
|
1203
|
+
this.logger.error(`Exception from system database: ${err}`, err);
|
|
1204
|
+
throw err;
|
|
1205
|
+
}
|
|
1206
|
+
const ct = Date.now();
|
|
1207
|
+
if (finishTime && ct > finishTime)
|
|
1208
|
+
return undefined; // Time's up
|
|
1209
|
+
if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
|
|
1210
|
+
finishTime = await this.#durableSleep(callerID, timerFuncID, timeoutms);
|
|
1242
1211
|
}
|
|
1212
|
+
let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
|
|
1213
|
+
poll = Math.min(pollIntervalMs, poll);
|
|
1214
|
+
await (0, utils_1.sleepms)(poll);
|
|
1243
1215
|
}
|
|
1244
1216
|
}
|
|
1245
|
-
async awaitFirstWorkflowId(workflowIds, callerID) {
|
|
1217
|
+
async awaitFirstWorkflowId(workflowIds, callerID, pollingIntervalMs) {
|
|
1246
1218
|
const placeholders = workflowIds.map((_, i) => `$${i + 1}`).join(', ');
|
|
1219
|
+
const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
|
|
1247
1220
|
while (true) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
finally {
|
|
1276
|
-
for (const h of cbHandles) {
|
|
1277
|
-
this.cancelWakeupMap.deregisterCallback(h);
|
|
1278
|
-
}
|
|
1279
|
-
if (callerCbHandle)
|
|
1280
|
-
this.cancelWakeupMap.deregisterCallback(callerCbHandle);
|
|
1221
|
+
if (callerID)
|
|
1222
|
+
await this.checkIfCanceled(callerID);
|
|
1223
|
+
const { rows } = await this.pool.query(`SELECT workflow_uuid FROM "${this.schemaName}".workflow_status
|
|
1224
|
+
WHERE workflow_uuid IN (${placeholders})
|
|
1225
|
+
AND status NOT IN ('${workflow_1.StatusString.PENDING}', '${workflow_1.StatusString.ENQUEUED}', '${workflow_1.StatusString.DELAYED}')
|
|
1226
|
+
LIMIT 1`, workflowIds);
|
|
1227
|
+
if (rows.length > 0) {
|
|
1228
|
+
return rows[0].workflow_uuid;
|
|
1229
|
+
}
|
|
1230
|
+
await (0, utils_1.sleepms)(pollIntervalMs);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
async awaitWorkflowIds(workflowIds, callerID, pollingIntervalMs) {
|
|
1234
|
+
const remainingWorkflowIds = new Set(workflowIds);
|
|
1235
|
+
const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
|
|
1236
|
+
while (remainingWorkflowIds.size > 0) {
|
|
1237
|
+
const currentWorkflowIds = [...remainingWorkflowIds];
|
|
1238
|
+
if (callerID)
|
|
1239
|
+
await this.checkIfCanceled(callerID);
|
|
1240
|
+
const { rows } = await this.pool.query(`SELECT workflow_uuid FROM "${this.schemaName}".workflow_status
|
|
1241
|
+
WHERE workflow_uuid = ANY($1::text[])
|
|
1242
|
+
AND status NOT IN ('${workflow_1.StatusString.PENDING}', '${workflow_1.StatusString.ENQUEUED}', '${workflow_1.StatusString.DELAYED}')`, [currentWorkflowIds]);
|
|
1243
|
+
for (const row of rows) {
|
|
1244
|
+
remainingWorkflowIds.delete(row.workflow_uuid);
|
|
1245
|
+
}
|
|
1246
|
+
if (remainingWorkflowIds.size === 0) {
|
|
1247
|
+
return;
|
|
1281
1248
|
}
|
|
1249
|
+
await (0, utils_1.sleepms)(pollIntervalMs);
|
|
1282
1250
|
}
|
|
1283
1251
|
}
|
|
1284
1252
|
// ==================== Sleep ====================
|
|
1285
1253
|
async durableSleepms(workflowID, functionID, durationMS) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
resolveNotification = () => {
|
|
1290
|
-
cancelled = true;
|
|
1291
|
-
resolve();
|
|
1292
|
-
};
|
|
1293
|
-
});
|
|
1294
|
-
const cbr = this.cancelWakeupMap.registerCallback(workflowID, resolveNotification);
|
|
1295
|
-
try {
|
|
1296
|
-
const { cancel: cancelInitial, endTime } = await this.#durableSleep(workflowID, functionID, durationMS);
|
|
1297
|
-
cancelInitial();
|
|
1298
|
-
while (!cancelled && Date.now() < endTime) {
|
|
1299
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(Math.min(endTime - Date.now(), utils_1.sleepConfig.maxTimeoutMS));
|
|
1300
|
-
try {
|
|
1301
|
-
await Promise.race([cancelPromise, promise]);
|
|
1302
|
-
}
|
|
1303
|
-
finally {
|
|
1304
|
-
cancel();
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
finally {
|
|
1309
|
-
this.cancelWakeupMap.deregisterCallback(cbr);
|
|
1254
|
+
const endTime = await this.#durableSleep(workflowID, functionID, durationMS);
|
|
1255
|
+
while (Date.now() < endTime) {
|
|
1256
|
+
await (0, utils_1.sleepms)(Math.min(endTime - Date.now(), utils_1.sleepConfig.maxTimeoutMS));
|
|
1310
1257
|
}
|
|
1311
1258
|
await this.checkIfCanceled(workflowID);
|
|
1312
1259
|
}
|
|
@@ -1357,7 +1304,7 @@ class SystemDatabase {
|
|
|
1357
1304
|
throw err;
|
|
1358
1305
|
}
|
|
1359
1306
|
}
|
|
1360
|
-
async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec) {
|
|
1307
|
+
async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec, pollingIntervalMs) {
|
|
1361
1308
|
topic = topic ?? this.nullTopic;
|
|
1362
1309
|
const startTime = Date.now();
|
|
1363
1310
|
// First, check for previous executions.
|
|
@@ -1370,6 +1317,7 @@ class SystemDatabase {
|
|
|
1370
1317
|
}
|
|
1371
1318
|
const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
|
|
1372
1319
|
let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
|
|
1320
|
+
const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
|
|
1373
1321
|
while (true) {
|
|
1374
1322
|
// register the key with the global notifications listener.
|
|
1375
1323
|
let resolveNotification;
|
|
@@ -1378,9 +1326,6 @@ class SystemDatabase {
|
|
|
1378
1326
|
});
|
|
1379
1327
|
const payload = `${workflowID}::${topic}`;
|
|
1380
1328
|
const cbr = this.notificationsMap.registerCallback(payload, resolveNotification);
|
|
1381
|
-
const crh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
|
|
1382
|
-
resolveNotification();
|
|
1383
|
-
});
|
|
1384
1329
|
try {
|
|
1385
1330
|
await this.checkIfCanceled(workflowID);
|
|
1386
1331
|
// Check if the key is already in the DB, then wait for the notification if it isn't.
|
|
@@ -1390,31 +1335,21 @@ class SystemDatabase {
|
|
|
1390
1335
|
const ct = Date.now();
|
|
1391
1336
|
if (finishTime && ct > finishTime)
|
|
1392
1337
|
break; // Time's up
|
|
1393
|
-
let timeoutPromise = Promise.resolve();
|
|
1394
|
-
let timeoutCancel = () => { };
|
|
1395
1338
|
if (timeoutms) {
|
|
1396
|
-
|
|
1397
|
-
timeoutPromise = promise;
|
|
1398
|
-
timeoutCancel = cancel;
|
|
1399
|
-
finishTime = endTime;
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
|
|
1403
|
-
poll = Math.min(this.dbPollingIntervalEventMs, poll);
|
|
1404
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
|
1405
|
-
timeoutPromise = promise;
|
|
1406
|
-
timeoutCancel = cancel;
|
|
1339
|
+
finishTime = await this.#durableSleep(workflowID, timeoutFunctionID, timeoutms);
|
|
1407
1340
|
}
|
|
1341
|
+
let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
|
|
1342
|
+
poll = Math.min(pollIntervalMs, poll);
|
|
1343
|
+
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
|
1408
1344
|
try {
|
|
1409
|
-
await Promise.race([messagePromise,
|
|
1345
|
+
await Promise.race([messagePromise, promise]);
|
|
1410
1346
|
}
|
|
1411
1347
|
finally {
|
|
1412
|
-
|
|
1348
|
+
cancel();
|
|
1413
1349
|
}
|
|
1414
1350
|
}
|
|
1415
1351
|
finally {
|
|
1416
1352
|
this.notificationsMap.deregisterCallback(cbr);
|
|
1417
|
-
this.cancelWakeupMap.deregisterCallback(crh);
|
|
1418
1353
|
}
|
|
1419
1354
|
}
|
|
1420
1355
|
await this.checkIfCanceled(workflowID);
|
|
@@ -1488,7 +1423,7 @@ class SystemDatabase {
|
|
|
1488
1423
|
client.release();
|
|
1489
1424
|
}
|
|
1490
1425
|
}
|
|
1491
|
-
async getEvent(workflowID, key, timeoutSeconds, callerWorkflow) {
|
|
1426
|
+
async getEvent(workflowID, key, timeoutSeconds, callerWorkflow, pollingIntervalMs) {
|
|
1492
1427
|
const startTime = Date.now();
|
|
1493
1428
|
// Check if the operation has been done before for OAOO (only do this inside a workflow).
|
|
1494
1429
|
if (callerWorkflow) {
|
|
@@ -1506,6 +1441,7 @@ class SystemDatabase {
|
|
|
1506
1441
|
const payloadKey = `${workflowID}::${key}`;
|
|
1507
1442
|
const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
|
|
1508
1443
|
let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
|
|
1444
|
+
const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
|
|
1509
1445
|
// Register the key with the global notifications listener first... we do not want to look in the DB first
|
|
1510
1446
|
// or that would cause a timing hole.
|
|
1511
1447
|
while (true) {
|
|
@@ -1514,11 +1450,6 @@ class SystemDatabase {
|
|
|
1514
1450
|
resolveNotification = resolve;
|
|
1515
1451
|
});
|
|
1516
1452
|
const cbr = this.workflowEventsMap.registerCallback(payloadKey, resolveNotification);
|
|
1517
|
-
const crh = callerWorkflow?.workflowID
|
|
1518
|
-
? this.cancelWakeupMap.registerCallback(callerWorkflow.workflowID, (_res) => {
|
|
1519
|
-
resolveNotification();
|
|
1520
|
-
})
|
|
1521
|
-
: undefined;
|
|
1522
1453
|
try {
|
|
1523
1454
|
if (callerWorkflow?.workflowID)
|
|
1524
1455
|
await this.checkIfCanceled(callerWorkflow?.workflowID);
|
|
@@ -1535,32 +1466,21 @@ class SystemDatabase {
|
|
|
1535
1466
|
if (finishTime && ct > finishTime)
|
|
1536
1467
|
break; // Time's up
|
|
1537
1468
|
// If we have a callerWorkflow, we want a durable sleep, otherwise, not
|
|
1538
|
-
let timeoutPromise = Promise.resolve();
|
|
1539
|
-
let timeoutCancel = () => { };
|
|
1540
1469
|
if (callerWorkflow && timeoutms) {
|
|
1541
|
-
|
|
1542
|
-
timeoutPromise = promise;
|
|
1543
|
-
timeoutCancel = cancel;
|
|
1544
|
-
finishTime = endTime;
|
|
1545
|
-
}
|
|
1546
|
-
else {
|
|
1547
|
-
let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
|
|
1548
|
-
poll = Math.min(this.dbPollingIntervalEventMs, poll);
|
|
1549
|
-
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
|
1550
|
-
timeoutPromise = promise;
|
|
1551
|
-
timeoutCancel = cancel;
|
|
1470
|
+
finishTime = await this.#durableSleep(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms);
|
|
1552
1471
|
}
|
|
1472
|
+
let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
|
|
1473
|
+
poll = Math.min(pollIntervalMs, poll);
|
|
1474
|
+
const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
|
|
1553
1475
|
try {
|
|
1554
|
-
await Promise.race([valuePromise,
|
|
1476
|
+
await Promise.race([valuePromise, promise]);
|
|
1555
1477
|
}
|
|
1556
1478
|
finally {
|
|
1557
|
-
|
|
1479
|
+
cancel();
|
|
1558
1480
|
}
|
|
1559
1481
|
}
|
|
1560
1482
|
finally {
|
|
1561
1483
|
this.workflowEventsMap.deregisterCallback(cbr);
|
|
1562
|
-
if (crh)
|
|
1563
|
-
this.cancelWakeupMap.deregisterCallback(crh);
|
|
1564
1484
|
}
|
|
1565
1485
|
}
|
|
1566
1486
|
// Record the output if it is inside a workflow.
|
|
@@ -2758,6 +2678,10 @@ class SystemDatabase {
|
|
|
2758
2678
|
const param = args.push(where.status);
|
|
2759
2679
|
whereClause += ` AND status=$${param}`;
|
|
2760
2680
|
}
|
|
2681
|
+
if (where.notStatus) {
|
|
2682
|
+
const param = args.push(where.notStatus);
|
|
2683
|
+
whereClause += ` AND status!=$${param}`;
|
|
2684
|
+
}
|
|
2761
2685
|
const result = await client.query(`UPDATE "${this.schemaName}".workflow_status ${setClause} ${whereClause}`, args);
|
|
2762
2686
|
const throwOnFailure = options.throwOnFailure ?? true;
|
|
2763
2687
|
if (throwOnFailure && result.rowCount !== 1) {
|
|
@@ -2832,31 +2756,18 @@ class SystemDatabase {
|
|
|
2832
2756
|
});
|
|
2833
2757
|
return output;
|
|
2834
2758
|
}
|
|
2835
|
-
#setWFCancelMap(workflowID) {
|
|
2836
|
-
if (this.runningWorkflowMap.has(workflowID)) {
|
|
2837
|
-
this.workflowCancellationMap.set(workflowID, true);
|
|
2838
|
-
}
|
|
2839
|
-
this.cancelWakeupMap.callCallbacks(workflowID);
|
|
2840
|
-
}
|
|
2841
|
-
#clearWFCancelMap(workflowID) {
|
|
2842
|
-
if (this.workflowCancellationMap.has(workflowID)) {
|
|
2843
|
-
this.workflowCancellationMap.delete(workflowID);
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
2759
|
async #checkIfCanceled(client, workflowID) {
|
|
2847
|
-
if (this.workflowCancellationMap.get(workflowID) === true) {
|
|
2848
|
-
throw new error_1.DBOSWorkflowCancelledError(workflowID);
|
|
2849
|
-
}
|
|
2850
2760
|
const statusValue = await this.getWorkflowStatusValue(client, workflowID);
|
|
2851
2761
|
if (statusValue === workflow_1.StatusString.CANCELLED) {
|
|
2852
2762
|
throw new error_1.DBOSWorkflowCancelledError(workflowID);
|
|
2853
2763
|
}
|
|
2854
2764
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2765
|
+
// Durably records (or, on recovery, reads back) the wakeup deadline for a sleep or
|
|
2766
|
+
// timeout so it survives recovery. Returns the absolute end time in epoch ms; the
|
|
2767
|
+
// caller is responsible for actually waiting until then. Throws if the workflow has
|
|
2768
|
+
// been cancelled.
|
|
2769
|
+
async #durableSleep(workflowID, functionID, durationMS) {
|
|
2770
|
+
const endTimeMs = Date.now() + durationMS;
|
|
2860
2771
|
const client = await this.pool.connect();
|
|
2861
2772
|
try {
|
|
2862
2773
|
const res = await this.#getOperationResultAndThrowIfCancelled(client, workflowID, functionID);
|
|
@@ -2864,18 +2775,13 @@ class SystemDatabase {
|
|
|
2864
2775
|
if (res.functionName !== exports.DBOS_FUNCNAME_SLEEP) {
|
|
2865
2776
|
throw new error_1.DBOSUnexpectedStepError(workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, res.functionName);
|
|
2866
2777
|
}
|
|
2867
|
-
|
|
2868
|
-
}
|
|
2869
|
-
else {
|
|
2870
|
-
await this.recordOperationResultInternal(client, workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, false, Date.now(), Date.now(), {
|
|
2871
|
-
output: serialization_1.DBOSPortableJSON.stringify(endTimeMs),
|
|
2872
|
-
serialization: serialization_1.DBOSPortableJSON.name(),
|
|
2873
|
-
});
|
|
2778
|
+
return JSON.parse(res.output);
|
|
2874
2779
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
};
|
|
2780
|
+
await this.recordOperationResultInternal(client, workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, false, Date.now(), Date.now(), {
|
|
2781
|
+
output: serialization_1.DBOSPortableJSON.stringify(endTimeMs),
|
|
2782
|
+
serialization: serialization_1.DBOSPortableJSON.name(),
|
|
2783
|
+
});
|
|
2784
|
+
return endTimeMs;
|
|
2879
2785
|
}
|
|
2880
2786
|
finally {
|
|
2881
2787
|
client.release();
|
|
@@ -3013,15 +2919,21 @@ __decorate([
|
|
|
3013
2919
|
__decorate([
|
|
3014
2920
|
dbRetry(),
|
|
3015
2921
|
__metadata("design:type", Function),
|
|
3016
|
-
__metadata("design:paramtypes", [String, Number, String, Number]),
|
|
2922
|
+
__metadata("design:paramtypes", [String, Number, String, Number, Number]),
|
|
3017
2923
|
__metadata("design:returntype", Promise)
|
|
3018
2924
|
], SystemDatabase.prototype, "awaitWorkflowResult", null);
|
|
3019
2925
|
__decorate([
|
|
3020
2926
|
dbRetry(),
|
|
3021
2927
|
__metadata("design:type", Function),
|
|
3022
|
-
__metadata("design:paramtypes", [Array, String]),
|
|
2928
|
+
__metadata("design:paramtypes", [Array, String, Number]),
|
|
3023
2929
|
__metadata("design:returntype", Promise)
|
|
3024
2930
|
], SystemDatabase.prototype, "awaitFirstWorkflowId", null);
|
|
2931
|
+
__decorate([
|
|
2932
|
+
dbRetry(),
|
|
2933
|
+
__metadata("design:type", Function),
|
|
2934
|
+
__metadata("design:paramtypes", [Array, String, Number]),
|
|
2935
|
+
__metadata("design:returntype", Promise)
|
|
2936
|
+
], SystemDatabase.prototype, "awaitWorkflowIds", null);
|
|
3025
2937
|
__decorate([
|
|
3026
2938
|
dbRetry(),
|
|
3027
2939
|
__metadata("design:type", Function),
|
|
@@ -3043,7 +2955,7 @@ __decorate([
|
|
|
3043
2955
|
__decorate([
|
|
3044
2956
|
dbRetry(),
|
|
3045
2957
|
__metadata("design:type", Function),
|
|
3046
|
-
__metadata("design:paramtypes", [String, Number, Number, String, Number]),
|
|
2958
|
+
__metadata("design:paramtypes", [String, Number, Number, String, Number, Number]),
|
|
3047
2959
|
__metadata("design:returntype", Promise)
|
|
3048
2960
|
], SystemDatabase.prototype, "recv", null);
|
|
3049
2961
|
__decorate([
|
|
@@ -3055,7 +2967,7 @@ __decorate([
|
|
|
3055
2967
|
__decorate([
|
|
3056
2968
|
dbRetry(),
|
|
3057
2969
|
__metadata("design:type", Function),
|
|
3058
|
-
__metadata("design:paramtypes", [String, String, Number, Object]),
|
|
2970
|
+
__metadata("design:paramtypes", [String, String, Number, Object, Number]),
|
|
3059
2971
|
__metadata("design:returntype", Promise)
|
|
3060
2972
|
], SystemDatabase.prototype, "getEvent", null);
|
|
3061
2973
|
__decorate([
|