@dbos-inc/dbos-sdk 2.8.46-preview.g30b0e27ed1 → 2.9.2-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.
Files changed (36) hide show
  1. package/dbos-config.schema.json +28 -19
  2. package/dist/dbos-config.schema.json +28 -19
  3. package/dist/schemas/system_db_schema.d.ts +0 -4
  4. package/dist/schemas/system_db_schema.d.ts.map +1 -1
  5. package/dist/src/client.js +2 -2
  6. package/dist/src/client.js.map +1 -1
  7. package/dist/src/dbos-executor.d.ts +3 -1
  8. package/dist/src/dbos-executor.d.ts.map +1 -1
  9. package/dist/src/dbos-executor.js +66 -28
  10. package/dist/src/dbos-executor.js.map +1 -1
  11. package/dist/src/dbos-runtime/config.js +2 -2
  12. package/dist/src/dbos-runtime/config.js.map +1 -1
  13. package/dist/src/dbos-runtime/workflow_management.d.ts.map +1 -1
  14. package/dist/src/dbos-runtime/workflow_management.js +1 -2
  15. package/dist/src/dbos-runtime/workflow_management.js.map +1 -1
  16. package/dist/src/dbos.d.ts +1 -8
  17. package/dist/src/dbos.d.ts.map +1 -1
  18. package/dist/src/dbos.js +8 -28
  19. package/dist/src/dbos.js.map +1 -1
  20. package/dist/src/error.d.ts +6 -11
  21. package/dist/src/error.d.ts.map +1 -1
  22. package/dist/src/error.js +16 -27
  23. package/dist/src/error.js.map +1 -1
  24. package/dist/src/scheduler/scheduler.js +1 -1
  25. package/dist/src/scheduler/scheduler.js.map +1 -1
  26. package/dist/src/system_database.d.ts +15 -42
  27. package/dist/src/system_database.d.ts.map +1 -1
  28. package/dist/src/system_database.js +99 -315
  29. package/dist/src/system_database.js.map +1 -1
  30. package/dist/src/workflow.d.ts.map +1 -1
  31. package/dist/src/workflow.js +38 -7
  32. package/dist/src/workflow.js.map +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +1 -1
  35. package/migrations/20250421000000_workflowcancel.js +0 -15
  36. package/migrations/20250421100000_triggers_wfcancel.js +0 -35
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
@@ -39,37 +40,6 @@ async function migrateSystemDatabase(systemPoolConfig, logger) {
39
40
  }
40
41
  }
41
42
  exports.migrateSystemDatabase = migrateSystemDatabase;
42
- class NotificationMap {
43
- map = new Map();
44
- curCK = 0;
45
- registerCallback(key, cb) {
46
- if (!this.map.has(key)) {
47
- this.map.set(key, new Map());
48
- }
49
- const ck = this.curCK++;
50
- this.map.get(key).set(ck, cb);
51
- return { key, ck };
52
- }
53
- deregisterCallback(k) {
54
- if (!this.map.has(k.key))
55
- return;
56
- const sm = this.map.get(k.key);
57
- if (!sm.has(k.ck))
58
- return;
59
- sm.delete(k.ck);
60
- if (sm.size === 0) {
61
- this.map.delete(k.key);
62
- }
63
- }
64
- callCallbacks(key, event) {
65
- if (!this.map.has(key))
66
- return;
67
- const sm = this.map.get(key);
68
- for (const cb of sm.values()) {
69
- cb(event);
70
- }
71
- }
72
- }
73
43
  class PostgresSystemDatabase {
74
44
  pgPoolConfig;
75
45
  systemDatabaseName;
@@ -78,32 +48,9 @@ class PostgresSystemDatabase {
78
48
  pool;
79
49
  systemPoolConfig;
80
50
  knexDB;
81
- /*
82
- * Generally, notifications are asynchronous. One should:
83
- * Subscribe to updates
84
- * Read the database item in question
85
- * In response to updates, re-read the database item
86
- * Unsubscribe at the end
87
- * The notification mechanism is reliable in the sense that it will eventually deliver updates
88
- * or the DB connection will get dropped. The right thing to do if you lose connectivity to
89
- * the system DB is to exit the process and go through recovery... system DB writes, notifications,
90
- * etc may not have completed correctly, and recovery is the way to rebuild in-memory state.
91
- *
92
- * NOTE:
93
- * PG Notifications are not fully reliable.
94
- * Dropped connections are recoverable - you just need to restart and scan everything.
95
- * (The whole VM being the logical choice, so workflows can recover from any write failures.)
96
- * The real problem is, if the pipes out of the server are full... then notifications can be
97
- * dropped, and only the PG server log may note it. For those reasons, we do occasional polling
98
- */
99
51
  notificationsClient = null;
100
- dbPollingIntervalMs = 1000;
101
- shouldUseDBNotifications = true;
102
- notificationsMap = new NotificationMap();
103
- workflowEventsMap = new NotificationMap();
104
- cancelWakeupMap = new NotificationMap();
105
- runningWorkflowMap = new Map(); // Map from workflowID to workflow promise
106
- workflowCancellationMap = new Map(); // Map from workflowID to its cancellation status.
52
+ notificationsMap = {};
53
+ workflowEventsMap = {};
107
54
  constructor(pgPoolConfig, systemDatabaseName, logger, sysDbPoolSize) {
108
55
  this.pgPoolConfig = pgPoolConfig;
109
56
  this.systemDatabaseName = systemDatabaseName;
@@ -153,9 +100,7 @@ class PostgresSystemDatabase {
153
100
  finally {
154
101
  await pgSystemClient.end();
155
102
  }
156
- if (this.shouldUseDBNotifications) {
157
- await this.listenForNotifications();
158
- }
103
+ await this.listenForNotifications();
159
104
  }
160
105
  async destroy() {
161
106
  await this.knexDB.destroy();
@@ -178,7 +123,7 @@ class PostgresSystemDatabase {
178
123
  await pgSystemClient.query(`DROP DATABASE IF EXISTS ${dbosConfig.system_database};`);
179
124
  await pgSystemClient.end();
180
125
  }
181
- async initWorkflowStatus(initStatus, serializedInputs) {
126
+ async initWorkflowStatus(initStatus, args) {
182
127
  const result = await this.pool.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status (
183
128
  workflow_uuid,
184
129
  status,
@@ -212,8 +157,8 @@ class PostgresSystemDatabase {
212
157
  initStatus.queueName,
213
158
  initStatus.authenticatedUser,
214
159
  initStatus.assumedRole,
215
- JSON.stringify(initStatus.authenticatedRoles),
216
- JSON.stringify(initStatus.request),
160
+ utils_1.DBOSJSON.stringify(initStatus.authenticatedRoles),
161
+ utils_1.DBOSJSON.stringify(initStatus.request),
217
162
  null,
218
163
  initStatus.executorId,
219
164
  initStatus.applicationVersion,
@@ -255,11 +200,12 @@ class PostgresSystemDatabase {
255
200
  }
256
201
  this.logger.debug(`Workflow ${initStatus.workflowUUID} attempt number: ${attempts}.`);
257
202
  const status = resRow.status;
203
+ const serializedInputs = utils_1.DBOSJSON.stringify(args);
258
204
  const { rows } = await this.pool.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_inputs (workflow_uuid, inputs) VALUES($1, $2) ON CONFLICT (workflow_uuid) DO UPDATE SET workflow_uuid = excluded.workflow_uuid RETURNING inputs`, [initStatus.workflowUUID, serializedInputs]);
259
205
  if (serializedInputs !== rows[0].inputs) {
260
206
  this.logger.warn(`Workflow inputs for ${initStatus.workflowUUID} changed since the first call! Use the original inputs.`);
261
207
  }
262
- return { serializedInputs: rows[0].inputs, status };
208
+ return { args: utils_1.DBOSJSON.parse(rows[0].inputs), status };
263
209
  }
264
210
  async recordWorkflowStatusChange(workflowID, status, update, client) {
265
211
  let rec = '';
@@ -272,7 +218,7 @@ class PostgresSystemDatabase {
272
218
  const wRes = await (client ?? this.pool).query(`UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status
273
219
  SET ${rec} status=$2, output=$3, error=$4, updated_at=$5 WHERE workflow_uuid=$1`, [workflowID, status, update.output, update.error, Date.now()]);
274
220
  if (wRes.rowCount !== 1) {
275
- throw new error_1.DBOSWorkflowConflictError(`Attempt to record transition of nonexistent workflow ${workflowID}`);
221
+ throw new error_1.DBOSWorkflowConflictUUIDError(`Attempt to record transition of nonexistent workflow ${workflowID}`);
276
222
  }
277
223
  }
278
224
  async recordWorkflowOutput(workflowID, status) {
@@ -293,10 +239,9 @@ class PostgresSystemDatabase {
293
239
  if (rows.length === 0) {
294
240
  return null;
295
241
  }
296
- return rows[0].inputs;
242
+ return utils_1.DBOSJSON.parse(rows[0].inputs);
297
243
  }
298
244
  async getOperationResult(workflowID, functionID, client) {
299
- await this.checkIfCanceled(workflowID);
300
245
  const { rows } = await (client ?? this.pool).query(`SELECT output, error, child_workflow_id, function_name
301
246
  FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.operation_outputs
302
247
  WHERE workflow_uuid=$1 AND function_id=$2`, [workflowID, functionID]);
@@ -337,7 +282,7 @@ class PostgresSystemDatabase {
337
282
  const err = error;
338
283
  if (err.code === '40001' || err.code === '23505') {
339
284
  // Serialization and primary key conflict (Postgres).
340
- throw new error_1.DBOSWorkflowConflictError(workflowID);
285
+ throw new error_1.DBOSWorkflowConflictUUIDError(workflowID);
341
286
  }
342
287
  else {
343
288
  throw err;
@@ -362,30 +307,6 @@ class PostgresSystemDatabase {
362
307
  return serialOutput;
363
308
  }
364
309
  async durableSleepms(workflowID, functionID, durationMS) {
365
- let resolveNotification;
366
- const cancelPromise = new Promise((resolve) => {
367
- resolveNotification = resolve;
368
- });
369
- const cbr = this.cancelWakeupMap.registerCallback(workflowID, resolveNotification);
370
- try {
371
- let timeoutPromise = Promise.resolve();
372
- const { promise, cancel: timeoutCancel } = await this.durableSleepmsInternal(workflowID, functionID, durationMS);
373
- timeoutPromise = promise;
374
- try {
375
- await Promise.race([cancelPromise, timeoutPromise]);
376
- }
377
- finally {
378
- timeoutCancel();
379
- }
380
- }
381
- finally {
382
- this.cancelWakeupMap.deregisterCallback(cbr);
383
- }
384
- await this.checkIfCanceled(workflowID);
385
- }
386
- async durableSleepmsInternal(workflowID, functionID, durationMS, maxSleepPerIteration) {
387
- if (maxSleepPerIteration === undefined)
388
- maxSleepPerIteration = durationMS;
389
310
  const curTime = Date.now();
390
311
  let endTimeMs = curTime + durationMS;
391
312
  const res = await this.getOperationResult(workflowID, functionID);
@@ -398,10 +319,7 @@ class PostgresSystemDatabase {
398
319
  else {
399
320
  await this.recordOperationResult(workflowID, functionID, { serialOutput: JSON.stringify(endTimeMs), functionName: exports.DBOS_FUNCNAME_SLEEP }, false);
400
321
  }
401
- return {
402
- ...(0, utils_1.cancellableSleep)(Math.max(Math.min(maxSleepPerIteration, endTimeMs - curTime), 0)),
403
- endTime: endTimeMs,
404
- };
322
+ return (0, utils_1.cancellableSleep)(Math.max(endTimeMs - curTime, 0));
405
323
  }
406
324
  nullTopic = '__null__topic__';
407
325
  async send(workflowID, functionID, destinationID, message, topic) {
@@ -440,56 +358,37 @@ class PostgresSystemDatabase {
440
358
  }
441
359
  return res.res.res;
442
360
  }
443
- const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
444
- let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
445
- while (true) {
446
- // register the key with the global notifications listener.
361
+ // Check if the key is already in the DB, then wait for the notification if it isn't.
362
+ const initRecvRows = (await this.pool.query(`SELECT topic FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.notifications WHERE destination_uuid=$1 AND topic=$2;`, [workflowID, topic])).rows;
363
+ if (initRecvRows.length === 0) {
364
+ // Then, register the key with the global notifications listener.
447
365
  let resolveNotification;
448
366
  const messagePromise = new Promise((resolve) => {
449
367
  resolveNotification = resolve;
450
368
  });
451
369
  const payload = `${workflowID}::${topic}`;
452
- const cbr = this.notificationsMap.registerCallback(payload, resolveNotification);
453
- const crh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
454
- resolveNotification();
455
- });
370
+ this.notificationsMap[payload] = resolveNotification; // The resolver assignment in the Promise definition runs synchronously.
371
+ let timeoutPromise = Promise.resolve();
372
+ let timeoutCancel = () => { };
456
373
  try {
457
- await this.checkIfCanceled(workflowID);
458
- // Check if the key is already in the DB, then wait for the notification if it isn't.
459
- const initRecvRows = (await this.pool.query(`SELECT topic FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.notifications WHERE destination_uuid=$1 AND topic=$2;`, [workflowID, topic])).rows;
460
- if (initRecvRows.length !== 0)
461
- break;
462
- const ct = Date.now();
463
- if (finishTime && ct > finishTime)
464
- break; // Time's up
465
- let timeoutPromise = Promise.resolve();
466
- let timeoutCancel = () => { };
467
- if (timeoutms) {
468
- const { promise, cancel, endTime } = await this.durableSleepmsInternal(workflowID, timeoutFunctionID, timeoutms, this.dbPollingIntervalMs);
469
- timeoutPromise = promise;
470
- timeoutCancel = cancel;
471
- finishTime = endTime;
472
- }
473
- else {
474
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
475
- poll = Math.min(this.dbPollingIntervalMs, poll);
476
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
477
- timeoutPromise = promise;
478
- timeoutCancel = cancel;
479
- }
480
- try {
481
- await Promise.race([messagePromise, timeoutPromise]);
482
- }
483
- finally {
484
- timeoutCancel();
485
- }
374
+ const { promise, cancel } = await this.durableSleepms(workflowID, timeoutFunctionID, timeoutSeconds * 1000);
375
+ timeoutPromise = promise;
376
+ timeoutCancel = cancel;
377
+ }
378
+ catch (e) {
379
+ this.logger.error(e);
380
+ delete this.notificationsMap[payload];
381
+ timeoutCancel();
382
+ throw new Error('durable sleepms failed');
383
+ }
384
+ try {
385
+ await Promise.race([messagePromise, timeoutPromise]);
486
386
  }
487
387
  finally {
488
- this.notificationsMap.deregisterCallback(cbr);
489
- this.cancelWakeupMap.deregisterCallback(crh);
388
+ timeoutCancel();
389
+ delete this.notificationsMap[payload];
490
390
  }
491
391
  }
492
- await this.checkIfCanceled(workflowID);
493
392
  // Transactionally consume and return the message if it's in the DB, otherwise return null.
494
393
  let message = null;
495
394
  const client = await this.pool.connect();
@@ -563,49 +462,40 @@ class PostgresSystemDatabase {
563
462
  // Get the return the value. if it's in the DB, otherwise return null.
564
463
  let value = null;
565
464
  const payloadKey = `${workflowID}::${key}`;
566
- const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
567
- let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
568
465
  // Register the key with the global notifications listener first... we do not want to look in the DB first
569
466
  // or that would cause a timing hole.
570
- while (true) {
571
- let resolveNotification;
572
- const valuePromise = new Promise((resolve) => {
573
- resolveNotification = resolve;
574
- });
575
- const cbr = this.workflowEventsMap.registerCallback(payloadKey, resolveNotification);
576
- const crh = callerWorkflow?.workflowID
577
- ? this.cancelWakeupMap.registerCallback(callerWorkflow.workflowID, (_res) => {
578
- resolveNotification();
579
- })
580
- : undefined;
581
- try {
582
- if (callerWorkflow?.workflowID)
583
- await this.checkIfCanceled(callerWorkflow?.workflowID);
584
- // Check if the key is already in the DB, then wait for the notification if it isn't.
585
- const initRecvRows = (await this.pool.query(`
586
- SELECT key, value
587
- FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
588
- WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
589
- if (initRecvRows.length > 0) {
590
- value = initRecvRows[0].value;
591
- break;
592
- }
593
- const ct = Date.now();
594
- if (finishTime && ct > finishTime)
595
- break; // Time's up
467
+ let resolveNotification;
468
+ const valuePromise = new Promise((resolve) => {
469
+ resolveNotification = resolve;
470
+ });
471
+ this.workflowEventsMap[payloadKey] = resolveNotification; // The resolver assignment in the Promise definition runs synchronously.
472
+ try {
473
+ // Check if the key is already in the DB, then wait for the notification if it isn't.
474
+ const initRecvRows = (await this.pool.query(`
475
+ SELECT key, value
476
+ FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
477
+ WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
478
+ if (initRecvRows.length > 0) {
479
+ value = initRecvRows[0].value;
480
+ }
481
+ else {
596
482
  // If we have a callerWorkflow, we want a durable sleep, otherwise, not
597
483
  let timeoutPromise = Promise.resolve();
598
484
  let timeoutCancel = () => { };
599
- if (callerWorkflow && timeoutms) {
600
- const { promise, cancel, endTime } = await this.durableSleepmsInternal(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms, this.dbPollingIntervalMs);
601
- timeoutPromise = promise;
602
- timeoutCancel = cancel;
603
- finishTime = endTime;
485
+ if (callerWorkflow) {
486
+ try {
487
+ const { promise, cancel } = await this.durableSleepms(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutSeconds * 1000);
488
+ timeoutPromise = promise;
489
+ timeoutCancel = cancel;
490
+ }
491
+ catch (e) {
492
+ this.logger.error(e);
493
+ delete this.workflowEventsMap[payloadKey];
494
+ throw new Error('durable sleepms failed');
495
+ }
604
496
  }
605
497
  else {
606
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
607
- poll = Math.min(this.dbPollingIntervalMs, poll);
608
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
498
+ const { promise, cancel } = (0, utils_1.cancellableSleep)(timeoutSeconds * 1000);
609
499
  timeoutPromise = promise;
610
500
  timeoutCancel = cancel;
611
501
  }
@@ -615,12 +505,17 @@ class PostgresSystemDatabase {
615
505
  finally {
616
506
  timeoutCancel();
617
507
  }
508
+ const finalRecvRows = (await this.pool.query(`
509
+ SELECT value
510
+ FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_events
511
+ WHERE workflow_uuid=$1 AND key=$2;`, [workflowID, key])).rows;
512
+ if (finalRecvRows.length > 0) {
513
+ value = finalRecvRows[0].value;
514
+ }
618
515
  }
619
- finally {
620
- this.workflowEventsMap.deregisterCallback(cbr);
621
- if (crh)
622
- this.cancelWakeupMap.deregisterCallback(crh);
623
- }
516
+ }
517
+ finally {
518
+ delete this.workflowEventsMap[payloadKey];
624
519
  }
625
520
  // Record the output if it is inside a workflow.
626
521
  if (callerWorkflow) {
@@ -634,23 +529,10 @@ class PostgresSystemDatabase {
634
529
  async setWorkflowStatus(workflowID, status, resetRecoveryAttempts) {
635
530
  await this.recordWorkflowStatusChange(workflowID, status, { resetRecoveryAttempts });
636
531
  }
637
- setWFCancelMap(workflowID) {
638
- if (this.runningWorkflowMap.has(workflowID)) {
639
- this.workflowCancellationMap.set(workflowID, true);
640
- }
641
- this.cancelWakeupMap.callCallbacks(workflowID);
642
- }
643
- clearWFCancelMap(workflowID) {
644
- if (this.workflowCancellationMap.has(workflowID)) {
645
- this.workflowCancellationMap.delete(workflowID);
646
- }
647
- }
648
532
  async cancelWorkflow(workflowID) {
649
533
  const client = await this.pool.connect();
650
534
  try {
651
535
  await client.query('BEGIN');
652
- await client.query(`INSERT INTO ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_cancel(workflow_id)
653
- VALUES ($1)`, [workflowID]);
654
536
  // Remove workflow from queues table
655
537
  await client.query(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
656
538
  WHERE workflow_uuid = $1`, [workflowID]);
@@ -659,20 +541,12 @@ class PostgresSystemDatabase {
659
541
  await client.query('COMMIT');
660
542
  }
661
543
  catch (error) {
662
- this.logger.error(error);
663
544
  await client.query('ROLLBACK');
664
545
  throw error;
665
546
  }
666
547
  finally {
667
548
  client.release();
668
549
  }
669
- this.setWFCancelMap(workflowID);
670
- }
671
- async checkIfCanceled(workflowID) {
672
- if (this.workflowCancellationMap.get(workflowID) === true) {
673
- throw new error_1.DBOSWorkflowCancelledError(workflowID);
674
- }
675
- return Promise.resolve();
676
550
  }
677
551
  async resumeWorkflow(workflowID) {
678
552
  const client = await this.pool.connect();
@@ -690,48 +564,17 @@ class PostgresSystemDatabase {
690
564
  // Remove the workflow from the queues table so resume can safely be called on an ENQUEUED workflow
691
565
  await client.query(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
692
566
  WHERE workflow_uuid = $1`, [workflowID]);
693
- await client.query(`DELETE FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_cancel
694
- WHERE workflow_id = $1`, [workflowID]);
695
567
  // Update status to pending and reset recovery attempts
696
568
  await this.recordWorkflowStatusChange(workflowID, workflow_1.StatusString.PENDING, { resetRecoveryAttempts: true }, client);
697
569
  await client.query('COMMIT');
698
570
  }
699
571
  catch (error) {
700
- this.logger.error(error);
701
572
  await client.query('ROLLBACK');
702
573
  throw error;
703
574
  }
704
575
  finally {
705
576
  client.release();
706
577
  }
707
- this.clearWFCancelMap(workflowID);
708
- }
709
- registerRunningWorkflow(workflowID, workflowPromise) {
710
- // Need to await for the workflow and capture errors.
711
- const awaitWorkflowPromise = workflowPromise
712
- .catch((error) => {
713
- this.logger.debug('Captured error in awaitWorkflowPromise: ' + error);
714
- })
715
- .finally(() => {
716
- // Remove itself from pending workflow map.
717
- this.runningWorkflowMap.delete(workflowID);
718
- this.workflowCancellationMap.delete(workflowID);
719
- });
720
- this.runningWorkflowMap.set(workflowID, awaitWorkflowPromise);
721
- }
722
- async awaitRunningWorkflows() {
723
- if (this.runningWorkflowMap.size > 0) {
724
- this.logger.info('Waiting for pending workflows to finish.');
725
- await Promise.allSettled(this.runningWorkflowMap.values());
726
- }
727
- if (this.workflowEventsMap.map.size > 0) {
728
- this.logger.warn('Workflow events map is not empty - shutdown is not clean.');
729
- //throw new Error('Workflow events map is not empty - shutdown is not clean.');
730
- }
731
- if (this.notificationsMap.map.size > 0) {
732
- this.logger.warn('Message notification map is not empty - shutdown is not clean.');
733
- //throw new Error('Message notification map is not empty - shutdown is not clean.');
734
- }
735
578
  }
736
579
  async getWorkflowStatus(workflowID, callerID, callerFN) {
737
580
  const internalStatus = await this.getWorkflowStatusInternal(workflowID, callerID, callerFN);
@@ -768,8 +611,8 @@ class PostgresSystemDatabase {
768
611
  queueName: rows[0].queue_name || undefined,
769
612
  authenticatedUser: rows[0].authenticated_user,
770
613
  assumedRole: rows[0].assumed_role,
771
- authenticatedRoles: JSON.parse(rows[0].authenticated_roles),
772
- request: JSON.parse(rows[0].request),
614
+ authenticatedRoles: utils_1.DBOSJSON.parse(rows[0].authenticated_roles),
615
+ request: utils_1.DBOSJSON.parse(rows[0].request),
773
616
  executorId: rows[0].executor_id,
774
617
  createdAt: Number(rows[0].created_at),
775
618
  updatedAt: Number(rows[0].updated_at),
@@ -783,79 +626,34 @@ class PostgresSystemDatabase {
783
626
  }, exports.DBOS_FUNCNAME_GETSTATUS, callerID, callerFN);
784
627
  return sv ? JSON.parse(sv) : null;
785
628
  }
786
- async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID) {
787
- const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
788
- let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
629
+ async awaitWorkflowResult(workflowID, timeoutms) {
630
+ const pollingIntervalMs = 1000;
631
+ const et = timeoutms !== undefined ? new Date().getTime() + timeoutms : undefined;
789
632
  while (true) {
790
- let resolveNotification;
791
- const statusPromise = new Promise((resolve) => {
792
- resolveNotification = resolve;
793
- });
794
- const irh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
795
- resolveNotification();
796
- });
797
- const crh = callerID
798
- ? this.cancelWakeupMap.registerCallback(callerID, (_res) => {
799
- resolveNotification();
800
- })
801
- : undefined;
802
- try {
803
- if (callerID)
804
- await this.checkIfCanceled(callerID);
805
- try {
806
- const { rows } = await this.pool.query(`SELECT status, output, error FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status WHERE workflow_uuid=$1`, [workflowID]);
807
- if (rows.length > 0) {
808
- const status = rows[0].status;
809
- if (status === workflow_1.StatusString.SUCCESS) {
810
- return { res: rows[0].output };
811
- }
812
- else if (status === workflow_1.StatusString.ERROR) {
813
- return { err: rows[0].error };
814
- }
815
- else if (status === workflow_1.StatusString.CANCELLED) {
816
- return { cancelled: true };
817
- }
818
- else {
819
- // Status is not actionable
820
- }
821
- }
633
+ const { rows } = await this.pool.query(`SELECT status, output, error FROM ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_status WHERE workflow_uuid=$1`, [workflowID]);
634
+ if (rows.length > 0) {
635
+ const status = rows[0].status;
636
+ if (status === workflow_1.StatusString.SUCCESS) {
637
+ return { res: rows[0].output };
822
638
  }
823
- catch (e) {
824
- const err = e;
825
- this.logger.error(`Exception from system database: ${err}`);
826
- throw err;
639
+ else if (status === workflow_1.StatusString.ERROR) {
640
+ return { err: rows[0].error };
827
641
  }
828
- const ct = Date.now();
829
- if (finishTime && ct > finishTime)
830
- return undefined; // Time's up
831
- let timeoutPromise = Promise.resolve();
832
- let timeoutCancel = () => { };
833
- if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
834
- const { promise, cancel, endTime } = await this.durableSleepmsInternal(callerID, timerFuncID, timeoutms, this.dbPollingIntervalMs);
835
- finishTime = endTime;
836
- timeoutPromise = promise;
837
- timeoutCancel = cancel;
642
+ }
643
+ if (et !== undefined) {
644
+ const ct = new Date().getTime();
645
+ if (et > ct) {
646
+ await (0, utils_1.sleepms)(Math.min(pollingIntervalMs, et - ct));
838
647
  }
839
648
  else {
840
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalMs;
841
- poll = Math.min(this.dbPollingIntervalMs, poll);
842
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
843
- timeoutPromise = promise;
844
- timeoutCancel = cancel;
845
- }
846
- try {
847
- await Promise.race([statusPromise, timeoutPromise]);
848
- }
849
- finally {
850
- timeoutCancel();
649
+ break;
851
650
  }
852
651
  }
853
- finally {
854
- this.cancelWakeupMap.deregisterCallback(irh);
855
- if (crh)
856
- this.cancelWakeupMap.deregisterCallback(crh);
652
+ else {
653
+ await (0, utils_1.sleepms)(pollingIntervalMs);
857
654
  }
858
655
  }
656
+ return undefined;
859
657
  }
860
658
  /* BACKGROUND PROCESSES */
861
659
  /**
@@ -866,29 +664,15 @@ class PostgresSystemDatabase {
866
664
  this.notificationsClient = await this.pool.connect();
867
665
  await this.notificationsClient.query('LISTEN dbos_notifications_channel;');
868
666
  await this.notificationsClient.query('LISTEN dbos_workflow_events_channel;');
869
- await this.notificationsClient.query('LISTEN dbos_workflow_cancel_channel;');
870
667
  const handler = (msg) => {
871
- if (!this.shouldUseDBNotifications)
872
- return; // Testing parameter
873
668
  if (msg.channel === 'dbos_notifications_channel') {
874
- if (msg.payload) {
875
- this.notificationsMap.callCallbacks(msg.payload);
876
- }
877
- }
878
- else if (msg.channel === 'dbos_workflow_events_channel') {
879
- if (msg.payload) {
880
- this.workflowEventsMap.callCallbacks(msg.payload);
669
+ if (msg.payload && msg.payload in this.notificationsMap) {
670
+ this.notificationsMap[msg.payload]();
881
671
  }
882
672
  }
883
- else if (msg.channel === 'dbos_workflow_cancel_channel') {
884
- if (msg.payload) {
885
- const notif = JSON.parse(msg.payload);
886
- if (notif.cancelled === 't') {
887
- this.setWFCancelMap(notif.wfid);
888
- }
889
- else {
890
- this.clearWFCancelMap(notif.wfid);
891
- }
673
+ else {
674
+ if (msg.payload && msg.payload in this.workflowEventsMap) {
675
+ this.workflowEventsMap[msg.payload]();
892
676
  }
893
677
  }
894
678
  };
@@ -1085,7 +869,7 @@ class PostgresSystemDatabase {
1085
869
  }
1086
870
  async dequeueWorkflow(workflowId, queue) {
1087
871
  if (queue.rateLimit) {
1088
- const time = Date.now();
872
+ const time = new Date().getTime();
1089
873
  await this.pool.query(`
1090
874
  UPDATE ${dbos_executor_1.DBOSExecutor.systemDBSchemaName}.workflow_queue
1091
875
  SET completed_at_epoch_ms = $2
@@ -1100,7 +884,7 @@ class PostgresSystemDatabase {
1100
884
  }
1101
885
  }
1102
886
  async findAndMarkStartableWorkflows(queue, executorID, appVersion) {
1103
- const startTimeMs = Date.now();
887
+ const startTimeMs = new Date().getTime();
1104
888
  const limiterPeriodMS = queue.rateLimit ? queue.rateLimit.periodSec * 1000 : 0;
1105
889
  const claimedIDs = [];
1106
890
  await this.knexDB.transaction(async (trx) => {