@dbos-inc/dbos-sdk 3.0.23-preview → 3.0.27-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/authdecorators.d.ts +0 -7
- package/dist/src/authdecorators.d.ts.map +1 -1
- package/dist/src/authdecorators.js +1 -29
- package/dist/src/authdecorators.js.map +1 -1
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js.map +1 -1
- package/dist/src/context.d.ts +10 -44
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +34 -114
- package/dist/src/context.js.map +1 -1
- package/dist/src/datasource.d.ts.map +1 -1
- package/dist/src/datasource.js +37 -17
- package/dist/src/datasource.js.map +1 -1
- package/dist/src/dbos-executor.d.ts +32 -37
- package/dist/src/dbos-executor.d.ts.map +1 -1
- package/dist/src/dbos-executor.js +263 -267
- package/dist/src/dbos-executor.js.map +1 -1
- package/dist/src/dbos-runtime/cli.d.ts.map +1 -1
- package/dist/src/dbos-runtime/cli.js +1 -3
- package/dist/src/dbos-runtime/cli.js.map +1 -1
- package/dist/src/dbos-runtime/debug.d.ts +1 -1
- package/dist/src/dbos-runtime/debug.d.ts.map +1 -1
- package/dist/src/dbos-runtime/debug.js +2 -3
- package/dist/src/dbos-runtime/debug.js.map +1 -1
- package/dist/src/dbos.d.ts +6 -21
- package/dist/src/dbos.d.ts.map +1 -1
- package/dist/src/dbos.js +98 -268
- package/dist/src/dbos.js.map +1 -1
- package/dist/src/decorators.d.ts +7 -7
- package/dist/src/decorators.d.ts.map +1 -1
- package/dist/src/decorators.js +6 -30
- package/dist/src/decorators.js.map +1 -1
- package/dist/src/eventreceiver.d.ts +7 -10
- package/dist/src/eventreceiver.d.ts.map +1 -1
- package/dist/src/httpServer/handler.d.ts +1 -9
- package/dist/src/httpServer/handler.d.ts.map +1 -1
- package/dist/src/httpServer/handler.js.map +1 -1
- package/dist/src/httpServer/middleware.d.ts +2 -2
- package/dist/src/httpServer/middleware.d.ts.map +1 -1
- package/dist/src/httpServer/server.d.ts +3 -3
- package/dist/src/httpServer/server.d.ts.map +1 -1
- package/dist/src/httpServer/server.js +19 -21
- package/dist/src/httpServer/server.js.map +1 -1
- package/dist/src/index.d.ts +3 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -13
- package/dist/src/index.js.map +1 -1
- package/dist/src/paramdecorators.d.ts.map +1 -1
- package/dist/src/paramdecorators.js +0 -6
- package/dist/src/paramdecorators.js.map +1 -1
- package/dist/src/procedure.d.ts +0 -22
- package/dist/src/procedure.d.ts.map +1 -1
- package/dist/src/procedure.js +0 -16
- package/dist/src/procedure.js.map +1 -1
- package/dist/src/scheduler/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/step.d.ts +0 -25
- package/dist/src/step.d.ts.map +1 -1
- package/dist/src/step.js +0 -20
- package/dist/src/step.js.map +1 -1
- package/dist/src/system_database.d.ts +4 -4
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/telemetry/logs.d.ts +4 -8
- package/dist/src/telemetry/logs.d.ts.map +1 -1
- package/dist/src/telemetry/logs.js +25 -13
- package/dist/src/telemetry/logs.js.map +1 -1
- package/dist/src/transaction.d.ts +0 -25
- package/dist/src/transaction.d.ts.map +1 -1
- package/dist/src/transaction.js +1 -15
- package/dist/src/transaction.js.map +1 -1
- package/dist/src/user_database.d.ts +2 -2
- package/dist/src/user_database.d.ts.map +1 -1
- package/dist/src/user_database.js.map +1 -1
- package/dist/src/workflow.d.ts +4 -148
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +7 -226
- package/dist/src/workflow.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
@@ -26,11 +26,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
27
27
|
};
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
29
|
-
exports.DBOSExecutor = exports.TempWorkflowType = exports.OperationType = exports.
|
29
|
+
exports.DBOSExecutor = exports.TempWorkflowType = exports.OperationType = exports.isDeprecatedDBOSConfig = exports.DBOS_QUEUE_MAX_PRIORITY = exports.DBOS_QUEUE_MIN_PRIORITY = exports.dbosNull = void 0;
|
30
30
|
const error_1 = require("./error");
|
31
31
|
const workflow_1 = require("./workflow");
|
32
32
|
const transaction_1 = require("./transaction");
|
33
|
-
const step_1 = require("./step");
|
34
33
|
const collector_1 = require("./telemetry/collector");
|
35
34
|
const traces_1 = require("./telemetry/traces");
|
36
35
|
const logs_1 = require("./telemetry/logs");
|
@@ -46,7 +45,6 @@ const context_1 = require("./context");
|
|
46
45
|
const serialize_error_1 = require("serialize-error");
|
47
46
|
const utils_1 = require("./utils");
|
48
47
|
const node_path_1 = __importDefault(require("node:path"));
|
49
|
-
const procedure_1 = require("./procedure");
|
50
48
|
const _1 = require(".");
|
51
49
|
const lodash_1 = require("lodash");
|
52
50
|
const wfqueue_1 = require("./wfqueue");
|
@@ -67,17 +65,11 @@ function isDeprecatedDBOSConfig(config) {
|
|
67
65
|
return isDeprecated;
|
68
66
|
}
|
69
67
|
exports.isDeprecatedDBOSConfig = isDeprecatedDBOSConfig;
|
70
|
-
var DebugMode;
|
71
|
-
(function (DebugMode) {
|
72
|
-
DebugMode[DebugMode["DISABLED"] = 0] = "DISABLED";
|
73
|
-
DebugMode[DebugMode["ENABLED"] = 1] = "ENABLED";
|
74
|
-
DebugMode[DebugMode["TIME_TRAVEL"] = 2] = "TIME_TRAVEL";
|
75
|
-
})(DebugMode || (exports.DebugMode = DebugMode = {}));
|
76
68
|
exports.OperationType = {
|
77
69
|
HANDLER: 'handler',
|
78
70
|
WORKFLOW: 'workflow',
|
79
71
|
TRANSACTION: 'transaction',
|
80
|
-
|
72
|
+
STEP: 'step',
|
81
73
|
PROCEDURE: 'procedure',
|
82
74
|
};
|
83
75
|
exports.TempWorkflowType = {
|
@@ -120,22 +112,9 @@ class DBOSExecutor {
|
|
120
112
|
telemetryCollector;
|
121
113
|
static defaultNotificationTimeoutSec = 60;
|
122
114
|
debugMode;
|
123
|
-
get isDebugging() {
|
124
|
-
switch (this.debugMode) {
|
125
|
-
case DebugMode.DISABLED:
|
126
|
-
return false;
|
127
|
-
case DebugMode.ENABLED:
|
128
|
-
case DebugMode.TIME_TRAVEL:
|
129
|
-
return true;
|
130
|
-
default: {
|
131
|
-
const _never = this.debugMode;
|
132
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
133
|
-
throw new Error(`Unexpected DBOS debug mode: ${this.debugMode}`);
|
134
|
-
}
|
135
|
-
}
|
136
|
-
}
|
137
115
|
static systemDBSchemaName = 'dbos';
|
138
116
|
logger;
|
117
|
+
ctxLogger;
|
139
118
|
tracer;
|
140
119
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
141
120
|
typeormEntities = [];
|
@@ -148,7 +127,7 @@ class DBOSExecutor {
|
|
148
127
|
/* WORKFLOW EXECUTOR LIFE CYCLE MANAGEMENT */
|
149
128
|
constructor(config, { systemDatabase, debugMode } = {}) {
|
150
129
|
this.config = config;
|
151
|
-
this.debugMode = debugMode ??
|
130
|
+
this.debugMode = debugMode ?? false;
|
152
131
|
// Set configured environment variables
|
153
132
|
if (config.env) {
|
154
133
|
for (const [key, value] of Object.entries(config.env)) {
|
@@ -169,8 +148,9 @@ class DBOSExecutor {
|
|
169
148
|
this.telemetryCollector = new collector_1.TelemetryCollector();
|
170
149
|
}
|
171
150
|
this.logger = new logs_1.GlobalLogger(this.telemetryCollector, this.config.telemetry.logs);
|
151
|
+
this.ctxLogger = new logs_1.DBOSContextualLogger(this.logger, () => (0, context_1.getCurrentContextStore)().span);
|
172
152
|
this.tracer = new traces_1.Tracer(this.telemetryCollector);
|
173
|
-
if (this.
|
153
|
+
if (this.debugMode) {
|
174
154
|
this.logger.info('Running in debug mode!');
|
175
155
|
}
|
176
156
|
this.procedurePool = new pg_1.Pool(this.config.poolConfig);
|
@@ -318,7 +298,7 @@ class DBOSExecutor {
|
|
318
298
|
}
|
319
299
|
this.logger.debug(`Loaded ${length} ORM entities`);
|
320
300
|
}
|
321
|
-
if (!this.
|
301
|
+
if (!this.debugMode) {
|
322
302
|
await (0, user_database_1.createDBIfDoesNotExist)(this.config.poolConfig, this.logger);
|
323
303
|
}
|
324
304
|
this.configureDbClient();
|
@@ -330,8 +310,8 @@ class DBOSExecutor {
|
|
330
310
|
this.#registerClass(cls);
|
331
311
|
}
|
332
312
|
// Debug mode doesn't need to initialize the DBs. Everything should appear to be read-only.
|
333
|
-
await this.userDatabase.init(this.
|
334
|
-
if (!this.
|
313
|
+
await this.userDatabase.init(this.debugMode);
|
314
|
+
if (!this.debugMode) {
|
335
315
|
await this.systemDatabase.init();
|
336
316
|
}
|
337
317
|
}
|
@@ -354,7 +334,7 @@ class DBOSExecutor {
|
|
354
334
|
}
|
355
335
|
this.initialized = true;
|
356
336
|
// Only execute init code if under non-debug mode
|
357
|
-
if (!this.
|
337
|
+
if (!this.debugMode) {
|
358
338
|
for (const cls of classnames) {
|
359
339
|
// Init its configurations
|
360
340
|
const creg = (0, decorators_1.getClassRegistrationByName)(cls);
|
@@ -536,7 +516,7 @@ class DBOSExecutor {
|
|
536
516
|
async workflow(wf, params, ...args) {
|
537
517
|
return this.internalWorkflow(wf, params, undefined, undefined, ...args);
|
538
518
|
}
|
539
|
-
// If
|
519
|
+
// If callerWFID and functionID are set, it means the workflow is invoked from within a workflow.
|
540
520
|
async internalWorkflow(wf, params, callerID, callerFunctionID, ...args) {
|
541
521
|
const workflowID = params.workflowUUID ? params.workflowUUID : (0, node_crypto_1.randomUUID)();
|
542
522
|
const presetID = params.workflowUUID ? true : false;
|
@@ -560,29 +540,40 @@ class DBOSExecutor {
|
|
560
540
|
this.logger.warn(`Priority is not enabled for queue ${params.queueName}. Setting priority will not have any effect.`);
|
561
541
|
}
|
562
542
|
}
|
543
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
563
544
|
const wInfo = this.getWorkflowInfo(wf);
|
564
545
|
if (wInfo === undefined) {
|
565
546
|
throw new error_1.DBOSNotRegisteredError(wf.name);
|
566
547
|
}
|
567
548
|
const wConfig = wInfo.config;
|
568
|
-
const
|
569
|
-
const
|
549
|
+
const maxRecoveryAttempts = wConfig.maxRecoveryAttempts ? wConfig.maxRecoveryAttempts : 50;
|
550
|
+
const wfname = wf.name; // TODO: Should be what was registered in wfInfo...
|
551
|
+
const span = this.tracer.startSpan(wfname, {
|
552
|
+
status: workflow_1.StatusString.PENDING,
|
553
|
+
operationUUID: workflowID,
|
554
|
+
operationType: exports.OperationType.WORKFLOW,
|
555
|
+
operationName: wInfo.registration?.name ?? wf.name,
|
556
|
+
authenticatedUser: pctx?.authenticatedUser ?? '',
|
557
|
+
authenticatedRoles: pctx?.authenticatedRoles ?? [],
|
558
|
+
assumedRole: pctx?.assumedRole ?? '',
|
559
|
+
}, pctx?.span);
|
560
|
+
const isTempWorkflow = DBOSExecutor.tempWorkflowName === wfname;
|
570
561
|
const internalStatus = {
|
571
562
|
workflowUUID: workflowID,
|
572
563
|
status: params.queueName !== undefined ? workflow_1.StatusString.ENQUEUED : workflow_1.StatusString.PENDING,
|
573
|
-
workflowName:
|
574
|
-
workflowClassName:
|
564
|
+
workflowName: wfname,
|
565
|
+
workflowClassName: isTempWorkflow ? '' : (0, decorators_1.getRegisteredMethodClassName)(wf),
|
575
566
|
workflowConfigName: params.configuredInstance?.name || '',
|
576
567
|
queueName: params.queueName,
|
577
|
-
authenticatedUser: wCtxt.authenticatedUser,
|
578
568
|
output: null,
|
579
569
|
error: null,
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
570
|
+
authenticatedUser: pctx?.authenticatedUser || '',
|
571
|
+
assumedRole: pctx?.assumedRole || '',
|
572
|
+
authenticatedRoles: pctx?.authenticatedRoles || [],
|
573
|
+
request: pctx?.request || {},
|
574
|
+
executorId: utils_1.globalParams.executorID,
|
584
575
|
applicationVersion: utils_1.globalParams.appVersion,
|
585
|
-
applicationID:
|
576
|
+
applicationID: utils_1.globalParams.appID,
|
586
577
|
createdAt: Date.now(), // Remember the start time of this workflow,
|
587
578
|
timeoutMS: timeoutMS,
|
588
579
|
deadlineEpochMS: deadlineEpochMS,
|
@@ -590,15 +581,15 @@ class DBOSExecutor {
|
|
590
581
|
deduplicationID: params.enqueueOptions?.deduplicationID,
|
591
582
|
priority: priority ?? 0,
|
592
583
|
};
|
593
|
-
if (
|
594
|
-
internalStatus.workflowName = `${DBOSExecutor.tempWorkflowName}-${
|
584
|
+
if (isTempWorkflow) {
|
585
|
+
internalStatus.workflowName = `${DBOSExecutor.tempWorkflowName}-${params.tempWfType}-${params.tempWfName}`;
|
595
586
|
internalStatus.workflowClassName = params.tempWfClass ?? '';
|
596
587
|
}
|
597
588
|
let status = undefined;
|
598
589
|
let $deadlineEpochMS = undefined;
|
599
590
|
// Synchronously set the workflow's status to PENDING and record workflow inputs.
|
600
591
|
// We have to do it for all types of workflows because operation_outputs table has a foreign key constraint on workflow status table.
|
601
|
-
if (this.
|
592
|
+
if (this.debugMode) {
|
602
593
|
const wfStatus = await this.systemDatabase.getWorkflowStatus(workflowID);
|
603
594
|
if (!wfStatus) {
|
604
595
|
throw new error_1.DBOSDebuggerError(`Failed to find inputs for workflow UUID ${workflowID}`);
|
@@ -616,7 +607,7 @@ class DBOSExecutor {
|
|
616
607
|
return new workflow_1.RetrievedHandle(this.systemDatabase, result.childWorkflowID, callerID, callerFunctionID);
|
617
608
|
}
|
618
609
|
}
|
619
|
-
const ires = await this.systemDatabase.initWorkflowStatus(internalStatus,
|
610
|
+
const ires = await this.systemDatabase.initWorkflowStatus(internalStatus, maxRecoveryAttempts);
|
620
611
|
if (callerFunctionID !== undefined && callerID !== undefined) {
|
621
612
|
await this.systemDatabase.recordOperationResult(callerID, callerFunctionID, internalStatus.workflowName, true, {
|
622
613
|
childWorkflowID: workflowID,
|
@@ -654,19 +645,24 @@ class DBOSExecutor {
|
|
654
645
|
e.dbos_already_logged = true;
|
655
646
|
internalStatus.error = utils_1.DBOSJSON.stringify((0, serialize_error_1.serializeError)(e));
|
656
647
|
internalStatus.status = workflow_1.StatusString.ERROR;
|
657
|
-
if (!exec.
|
648
|
+
if (!exec.debugMode) {
|
658
649
|
await exec.systemDatabase.recordWorkflowError(workflowID, internalStatus);
|
659
650
|
}
|
660
|
-
|
651
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e.message });
|
661
652
|
}
|
662
653
|
const runWorkflow = async () => {
|
663
654
|
let result;
|
664
655
|
// Execute the workflow.
|
665
656
|
try {
|
666
|
-
const callResult = await (0, context_1.
|
667
|
-
|
668
|
-
|
669
|
-
|
657
|
+
const callResult = await (0, context_1.runWithParentContext)(pctx, {
|
658
|
+
presetID,
|
659
|
+
timeoutMS,
|
660
|
+
deadlineEpochMS,
|
661
|
+
workflowId: workflowID,
|
662
|
+
span,
|
663
|
+
logger: this.ctxLogger,
|
664
|
+
}, () => {
|
665
|
+
const callPromise = wf.call(params.configuredInstance, ...args);
|
670
666
|
if ($deadlineEpochMS === undefined) {
|
671
667
|
return callPromise;
|
672
668
|
}
|
@@ -674,7 +670,7 @@ class DBOSExecutor {
|
|
674
670
|
return callPromiseWithTimeout(callPromise, $deadlineEpochMS, this.systemDatabase);
|
675
671
|
}
|
676
672
|
});
|
677
|
-
if (this.
|
673
|
+
if (this.debugMode) {
|
678
674
|
const recordedResult = DBOSExecutor.reviveResultOrError((await this.systemDatabase.awaitWorkflowResult(workflowID)));
|
679
675
|
if (!resultsMatch(recordedResult, callResult)) {
|
680
676
|
this.logger.error(`Detect different output for the workflow UUID ${workflowID}!\n Received: ${utils_1.DBOSJSON.stringify(callResult)}\n Original: ${utils_1.DBOSJSON.stringify(recordedResult)}`);
|
@@ -692,21 +688,21 @@ class DBOSExecutor {
|
|
692
688
|
}
|
693
689
|
internalStatus.output = utils_1.DBOSJSON.stringify(result);
|
694
690
|
internalStatus.status = workflow_1.StatusString.SUCCESS;
|
695
|
-
if (!this.
|
691
|
+
if (!this.debugMode) {
|
696
692
|
await this.systemDatabase.recordWorkflowOutput(workflowID, internalStatus);
|
697
693
|
}
|
698
|
-
|
694
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
699
695
|
}
|
700
696
|
catch (err) {
|
701
697
|
if (err instanceof error_1.DBOSWorkflowConflictError) {
|
702
698
|
// Retrieve the handle and wait for the result.
|
703
699
|
const retrievedHandle = this.retrieveWorkflow(workflowID);
|
704
700
|
result = await retrievedHandle.getResult();
|
705
|
-
|
706
|
-
|
701
|
+
span.setAttribute('cached', true);
|
702
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
707
703
|
}
|
708
704
|
else if (err instanceof error_1.DBOSWorkflowCancelledError) {
|
709
|
-
|
705
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err.message });
|
710
706
|
internalStatus.error = err.message;
|
711
707
|
if (err.workflowID === workflowID) {
|
712
708
|
internalStatus.status = workflow_1.StatusString.CANCELLED;
|
@@ -724,11 +720,11 @@ class DBOSExecutor {
|
|
724
720
|
}
|
725
721
|
}
|
726
722
|
finally {
|
727
|
-
this.tracer.endSpan(
|
723
|
+
this.tracer.endSpan(span);
|
728
724
|
}
|
729
725
|
return result;
|
730
726
|
};
|
731
|
-
if (this.
|
727
|
+
if (this.debugMode ||
|
732
728
|
(status !== 'SUCCESS' && status !== 'ERROR' && (params.queueName === undefined || params.executeWorkflow))) {
|
733
729
|
const workflowPromise = runWorkflow();
|
734
730
|
this.systemDatabase.registerRunningWorkflow(workflowID, workflowPromise);
|
@@ -794,7 +790,7 @@ class DBOSExecutor {
|
|
794
790
|
* Write a operation's output to the database.
|
795
791
|
*/
|
796
792
|
async #recordOutput(query, workflowUUID, funcID, txnSnapshot, output, isKeyConflict, function_name) {
|
797
|
-
if (this.
|
793
|
+
if (this.debugMode) {
|
798
794
|
throw new error_1.DBOSDebuggerError('Cannot record output in debug mode.');
|
799
795
|
}
|
800
796
|
try {
|
@@ -816,7 +812,7 @@ class DBOSExecutor {
|
|
816
812
|
* Record an error in an operation to the database.
|
817
813
|
*/
|
818
814
|
async #recordError(query, workflowUUID, funcID, txnSnapshot, err, isKeyConflict, function_name) {
|
819
|
-
if (this.
|
815
|
+
if (this.debugMode) {
|
820
816
|
throw new error_1.DBOSDebuggerError('Cannot record error in debug mode.');
|
821
817
|
}
|
822
818
|
try {
|
@@ -844,67 +840,61 @@ class DBOSExecutor {
|
|
844
840
|
async transaction(txn, params, ...args) {
|
845
841
|
return await (await this.startTransactionTempWF(txn, params, undefined, undefined, ...args)).getResult();
|
846
842
|
}
|
847
|
-
async startTransactionTempWF(txn, params,
|
843
|
+
async startTransactionTempWF(txn, params, callerWFID, callerFunctionID, ...args) {
|
848
844
|
// Create a workflow and call transaction.
|
849
|
-
const temp_workflow = async (
|
850
|
-
|
851
|
-
return await this.callTransactionFunction(txn, params.configuredInstance ?? null, ctxtImpl, ...args);
|
845
|
+
const temp_workflow = async (...args) => {
|
846
|
+
return await this.callTransactionFunction(txn, params.configuredInstance ?? null, ...args);
|
852
847
|
};
|
853
848
|
return await this.internalWorkflow(temp_workflow, {
|
854
849
|
...params,
|
855
850
|
tempWfType: exports.TempWorkflowType.transaction,
|
856
851
|
tempWfName: (0, decorators_1.getRegisteredMethodName)(txn),
|
857
852
|
tempWfClass: (0, decorators_1.getRegisteredMethodClassName)(txn),
|
858
|
-
},
|
853
|
+
}, callerWFID, callerFunctionID, ...args);
|
859
854
|
}
|
860
|
-
async callTransactionFunction(txn, clsinst,
|
855
|
+
async callTransactionFunction(txn, clsinst, ...args) {
|
861
856
|
const txnInfo = this.getTransactionInfo(txn);
|
862
857
|
if (txnInfo === undefined) {
|
863
858
|
throw new error_1.DBOSNotRegisteredError(txn.name);
|
864
859
|
}
|
865
|
-
|
860
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
861
|
+
const wfid = pctx.workflowId;
|
862
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
866
863
|
let retryWaitMillis = 1;
|
867
864
|
const backoffFactor = 1.5;
|
868
865
|
const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
|
869
|
-
const funcId =
|
866
|
+
const funcId = (0, context_1.functionIDGetIncrement)();
|
870
867
|
const span = this.tracer.startSpan(txn.name, {
|
871
|
-
operationUUID:
|
868
|
+
operationUUID: wfid,
|
872
869
|
operationType: exports.OperationType.TRANSACTION,
|
873
|
-
|
874
|
-
|
875
|
-
|
870
|
+
operationName: txn.name,
|
871
|
+
authenticatedUser: pctx.authenticatedUser ?? '',
|
872
|
+
assumedRole: pctx.assumedRole ?? '',
|
873
|
+
authenticatedRoles: pctx.authenticatedRoles ?? [],
|
876
874
|
isolationLevel: txnInfo.config.isolationLevel,
|
877
|
-
},
|
875
|
+
}, pctx.span);
|
878
876
|
while (true) {
|
879
|
-
await this.systemDatabase.checkIfCanceled(
|
877
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
880
878
|
let txn_snapshot = 'invalid';
|
881
879
|
let prevResultFound = false;
|
882
|
-
const workflowUUID = wfCtx.workflowUUID;
|
883
880
|
const wrappedTransaction = async (client) => {
|
884
|
-
const tCtxt = new transaction_1.TransactionContextImpl(this.userDatabase.getName(), client, wfCtx, span, this.logger, funcId, txn.name);
|
885
881
|
// If the UUID is preset, it is possible this execution previously happened. Check, and return its original result if it did.
|
886
882
|
// Note: It is possible to retrieve a generated ID from a workflow handle, run a concurrent execution, and cause trouble for yourself. We recommend against this.
|
887
883
|
let prevResult = exports.dbosNull;
|
888
884
|
const queryFunc = (sql, args) => this.userDatabase.queryWithClient(client, sql, ...args);
|
889
|
-
if (
|
890
|
-
const executionResult = await this.#checkExecution(queryFunc,
|
885
|
+
if (pctx.presetID) {
|
886
|
+
const executionResult = await this.#checkExecution(queryFunc, wfid, funcId, txn.name);
|
891
887
|
prevResult = executionResult.result;
|
892
888
|
txn_snapshot = executionResult.txn_snapshot;
|
893
889
|
if (prevResult !== exports.dbosNull) {
|
894
890
|
prevResultFound = true;
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
891
|
+
span.setAttribute('cached', true);
|
892
|
+
// Return/throw the previous result
|
893
|
+
if (prevResult instanceof Error) {
|
894
|
+
throw prevResult;
|
899
895
|
}
|
900
896
|
else {
|
901
|
-
|
902
|
-
if (prevResult instanceof Error) {
|
903
|
-
throw prevResult;
|
904
|
-
}
|
905
|
-
else {
|
906
|
-
return prevResult;
|
907
|
-
}
|
897
|
+
return prevResult;
|
908
898
|
}
|
909
899
|
}
|
910
900
|
}
|
@@ -912,27 +902,32 @@ class DBOSExecutor {
|
|
912
902
|
// Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
|
913
903
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(queryFunc);
|
914
904
|
}
|
915
|
-
if (this.
|
916
|
-
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the transaction: workflow UUID ${
|
905
|
+
if (this.debugMode && prevResult === exports.dbosNull) {
|
906
|
+
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the transaction: workflow UUID ${wfid}, step number ${funcId}`);
|
917
907
|
}
|
918
908
|
// Execute the user's transaction.
|
909
|
+
const ctxlog = this.ctxLogger;
|
919
910
|
const result = await (async function () {
|
920
911
|
try {
|
921
|
-
return await (0, context_1.
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
912
|
+
return await (0, context_1.runWithParentContext)(pctx, {
|
913
|
+
authenticatedRoles: pctx?.authenticatedRoles,
|
914
|
+
authenticatedUser: pctx?.authenticatedUser,
|
915
|
+
workflowId: wfid,
|
916
|
+
curTxFunctionId: funcId,
|
917
|
+
parentCtx: pctx,
|
918
|
+
sqlClient: client,
|
919
|
+
logger: ctxlog,
|
920
|
+
span,
|
921
|
+
}, async () => {
|
922
|
+
const tf = txn;
|
923
|
+
return await tf.call(clsinst, ...args);
|
929
924
|
});
|
930
925
|
}
|
931
926
|
catch (e) {
|
932
927
|
return e instanceof Error ? e : new Error(`${e}`);
|
933
928
|
}
|
934
929
|
})();
|
935
|
-
if (this.
|
930
|
+
if (this.debugMode) {
|
936
931
|
if (prevResult instanceof Error) {
|
937
932
|
throw prevResult;
|
938
933
|
}
|
@@ -949,13 +944,13 @@ class DBOSExecutor {
|
|
949
944
|
// Record the execution, commit, and return.
|
950
945
|
try {
|
951
946
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
952
|
-
const pg_txn_id = await this.#recordOutput(queryFunc,
|
953
|
-
|
947
|
+
const pg_txn_id = await this.#recordOutput(queryFunc, wfid, funcId, txn_snapshot, result, (error) => this.userDatabase.isKeyConflictError(error), txn.name);
|
948
|
+
span.setAttribute('pg_txn_id', pg_txn_id);
|
954
949
|
}
|
955
950
|
catch (error) {
|
956
951
|
if (this.userDatabase.isFailedSqlTransactionError(error)) {
|
957
|
-
this.logger.error(`Postgres aborted the ${txn.name} @DBOS.transaction of Workflow ${
|
958
|
-
throw new error_1.DBOSFailedSqlTransactionError(
|
952
|
+
this.logger.error(`Postgres aborted the ${txn.name} @DBOS.transaction of Workflow ${wfid}, but the function did not raise an exception. Please ensure that the @DBOS.transaction method raises an exception if the database transaction is aborted.`);
|
953
|
+
throw new error_1.DBOSFailedSqlTransactionError(wfid, txn.name);
|
959
954
|
}
|
960
955
|
else {
|
961
956
|
throw error;
|
@@ -985,7 +980,7 @@ class DBOSExecutor {
|
|
985
980
|
const e = err;
|
986
981
|
await this.userDatabase.transaction(async (client) => {
|
987
982
|
const func = (sql, args) => this.userDatabase.queryWithClient(client, sql, ...args);
|
988
|
-
await this.#recordError(func,
|
983
|
+
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) => this.userDatabase.isKeyConflictError(error), txn.name);
|
989
984
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
990
985
|
}
|
991
986
|
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e.message });
|
@@ -996,9 +991,8 @@ class DBOSExecutor {
|
|
996
991
|
}
|
997
992
|
async procedure(proc, params, ...args) {
|
998
993
|
// Create a workflow and call procedure.
|
999
|
-
const temp_workflow = async (
|
1000
|
-
|
1001
|
-
return this.callProcedureFunction(proc, ctxtImpl, ...args);
|
994
|
+
const temp_workflow = async (...args) => {
|
995
|
+
return this.callProcedureFunction(proc, ...args);
|
1002
996
|
};
|
1003
997
|
return await (await this.workflow(temp_workflow, {
|
1004
998
|
...params,
|
@@ -1007,27 +1001,30 @@ class DBOSExecutor {
|
|
1007
1001
|
tempWfClass: (0, decorators_1.getRegisteredMethodClassName)(proc),
|
1008
1002
|
}, ...args)).getResult();
|
1009
1003
|
}
|
1010
|
-
async callProcedureFunction(proc,
|
1004
|
+
async callProcedureFunction(proc, ...args) {
|
1011
1005
|
const procInfo = this.getProcedureInfo(proc);
|
1012
1006
|
if (procInfo === undefined) {
|
1013
1007
|
throw new error_1.DBOSNotRegisteredError(proc.name);
|
1014
1008
|
}
|
1015
|
-
|
1016
|
-
const
|
1017
|
-
|
1009
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
1010
|
+
const wfid = pctx.workflowId;
|
1011
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
1012
|
+
const executeLocally = this.debugMode || (procInfo.config.executeLocally ?? false);
|
1013
|
+
const funcId = (0, context_1.functionIDGetIncrement)();
|
1018
1014
|
const span = this.tracer.startSpan(proc.name, {
|
1019
|
-
operationUUID:
|
1015
|
+
operationUUID: wfid,
|
1020
1016
|
operationType: exports.OperationType.PROCEDURE,
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1017
|
+
operationName: proc.name,
|
1018
|
+
authenticatedUser: pctx.authenticatedUser ?? '',
|
1019
|
+
assumedRole: pctx.assumedRole ?? '',
|
1020
|
+
authenticatedRoles: pctx.authenticatedRoles ?? [],
|
1024
1021
|
isolationLevel: procInfo.config.isolationLevel,
|
1025
1022
|
executeLocally,
|
1026
|
-
},
|
1023
|
+
}, pctx.span);
|
1027
1024
|
try {
|
1028
1025
|
const result = executeLocally
|
1029
|
-
? await this.#callProcedureFunctionLocal(proc, args,
|
1030
|
-
: await this.#callProcedureFunctionRemote(proc, args,
|
1026
|
+
? await this.#callProcedureFunctionLocal(proc, args, span, procInfo, funcId)
|
1027
|
+
: await this.#callProcedureFunctionRemote(proc, args, span, procInfo.config, funcId);
|
1031
1028
|
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1032
1029
|
return result;
|
1033
1030
|
}
|
@@ -1040,35 +1037,30 @@ class DBOSExecutor {
|
|
1040
1037
|
this.tracer.endSpan(span);
|
1041
1038
|
}
|
1042
1039
|
}
|
1043
|
-
async #callProcedureFunctionLocal(proc, args,
|
1040
|
+
async #callProcedureFunctionLocal(proc, args, span, procInfo, funcId) {
|
1044
1041
|
let retryWaitMillis = 1;
|
1045
1042
|
const backoffFactor = 1.5;
|
1046
1043
|
const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
|
1044
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
1045
|
+
const wfid = pctx.workflowId;
|
1047
1046
|
while (true) {
|
1048
|
-
await this.systemDatabase.checkIfCanceled(
|
1047
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
1049
1048
|
let txn_snapshot = 'invalid';
|
1050
1049
|
const wrappedProcedure = async (client) => {
|
1051
|
-
const ctxt = new procedure_1.StoredProcedureContextImpl(client, wfCtx, span, this.logger, funcId, proc.name);
|
1052
1050
|
let prevResult = exports.dbosNull;
|
1053
1051
|
const queryFunc = (sql, args) => this.procedurePool.query(sql, args).then((v) => v.rows);
|
1054
|
-
if (
|
1055
|
-
const executionResult = await this.#checkExecution(queryFunc,
|
1052
|
+
if (pctx.presetID) {
|
1053
|
+
const executionResult = await this.#checkExecution(queryFunc, wfid, funcId, proc.name);
|
1056
1054
|
prevResult = executionResult.result;
|
1057
1055
|
txn_snapshot = executionResult.txn_snapshot;
|
1058
1056
|
if (prevResult !== exports.dbosNull) {
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1057
|
+
span.setAttribute('cached', true);
|
1058
|
+
// Return/throw the previous result
|
1059
|
+
if (prevResult instanceof Error) {
|
1060
|
+
throw prevResult;
|
1063
1061
|
}
|
1064
1062
|
else {
|
1065
|
-
|
1066
|
-
if (prevResult instanceof Error) {
|
1067
|
-
throw prevResult;
|
1068
|
-
}
|
1069
|
-
else {
|
1070
|
-
return prevResult;
|
1071
|
-
}
|
1063
|
+
return prevResult;
|
1072
1064
|
}
|
1073
1065
|
}
|
1074
1066
|
}
|
@@ -1076,27 +1068,36 @@ class DBOSExecutor {
|
|
1076
1068
|
// Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
|
1077
1069
|
txn_snapshot = await DBOSExecutor.#retrieveSnapshot(queryFunc);
|
1078
1070
|
}
|
1079
|
-
if (this.
|
1080
|
-
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the procedure: workflow UUID ${
|
1071
|
+
if (this.debugMode && prevResult === exports.dbosNull) {
|
1072
|
+
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the procedure: workflow UUID ${wfid}, step number ${funcId}`);
|
1081
1073
|
}
|
1082
1074
|
// Execute the user's transaction.
|
1075
|
+
const ctxlog = this.ctxLogger;
|
1083
1076
|
const result = await (async function () {
|
1084
1077
|
try {
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1078
|
+
// Check we are in a workflow context and not in a step / transaction already
|
1079
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
1080
|
+
if (!pctx)
|
1081
|
+
throw new error_1.DBOSInvalidWorkflowTransitionError();
|
1082
|
+
if (!(0, context_1.isInWorkflowCtx)(pctx))
|
1083
|
+
throw new error_1.DBOSInvalidWorkflowTransitionError();
|
1084
|
+
return await (0, context_1.runWithParentContext)(pctx, {
|
1085
|
+
curTxFunctionId: funcId,
|
1086
|
+
parentCtx: pctx,
|
1087
|
+
isInStoredProc: true,
|
1088
|
+
sqlClient: client,
|
1089
|
+
logger: ctxlog,
|
1090
|
+
span,
|
1091
|
+
}, async () => {
|
1092
|
+
const pf = proc;
|
1093
|
+
return await pf(...args);
|
1093
1094
|
});
|
1094
1095
|
}
|
1095
1096
|
catch (e) {
|
1096
1097
|
return e instanceof Error ? e : new Error(`${e}`);
|
1097
1098
|
}
|
1098
1099
|
})();
|
1099
|
-
if (this.
|
1100
|
+
if (this.debugMode) {
|
1100
1101
|
if (prevResult instanceof Error) {
|
1101
1102
|
throw prevResult;
|
1102
1103
|
}
|
@@ -1112,9 +1113,9 @@ class DBOSExecutor {
|
|
1112
1113
|
}
|
1113
1114
|
// Synchronously record the output of write transactions and obtain the transaction ID.
|
1114
1115
|
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1115
|
-
const pg_txn_id = await this.#recordOutput(func,
|
1116
|
+
const pg_txn_id = await this.#recordOutput(func, wfid, funcId, txn_snapshot, result, user_database_1.pgNodeIsKeyConflictError, proc.name);
|
1116
1117
|
// const pg_txn_id = await wfCtx.recordOutputProc<R>(client, funcId, txn_snapshot, result);
|
1117
|
-
|
1118
|
+
span.setAttribute('pg_txn_id', pg_txn_id);
|
1118
1119
|
return result;
|
1119
1120
|
};
|
1120
1121
|
try {
|
@@ -1125,7 +1126,7 @@ class DBOSExecutor {
|
|
1125
1126
|
return result;
|
1126
1127
|
}
|
1127
1128
|
catch (err) {
|
1128
|
-
if (!this.
|
1129
|
+
if (!this.debugMode) {
|
1129
1130
|
if (this.userDatabase.isRetriableTransactionError(err)) {
|
1130
1131
|
// serialization_failure in PostgreSQL
|
1131
1132
|
span.addEvent('TXN SERIALIZATION FAILURE', { retryWaitMillis: retryWaitMillis }, performance.now());
|
@@ -1139,32 +1140,34 @@ class DBOSExecutor {
|
|
1139
1140
|
const e = err;
|
1140
1141
|
await this.invokeStoredProcFunction(async (client) => {
|
1141
1142
|
const func = (sql, args) => client.query(sql, args).then((v) => v.rows);
|
1142
|
-
await this.#recordError(func,
|
1143
|
+
await this.#recordError(func, wfid, funcId, txn_snapshot, e, user_database_1.pgNodeIsKeyConflictError, proc.name);
|
1143
1144
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
1144
1145
|
await this.userDatabase.transaction(async (client) => {
|
1145
1146
|
const func = (sql, args) => this.userDatabase.queryWithClient(client, sql, ...args);
|
1146
|
-
await this.#recordError(func,
|
1147
|
+
await this.#recordError(func, wfid, funcId, txn_snapshot, e, (error) => this.userDatabase.isKeyConflictError(error), proc.name);
|
1147
1148
|
}, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
|
1148
1149
|
}
|
1149
1150
|
throw err;
|
1150
1151
|
}
|
1151
1152
|
}
|
1152
1153
|
}
|
1153
|
-
async #callProcedureFunctionRemote(proc, args,
|
1154
|
-
if (this.
|
1154
|
+
async #callProcedureFunctionRemote(proc, args, span, config, funcId) {
|
1155
|
+
if (this.debugMode) {
|
1155
1156
|
throw new error_1.DBOSDebuggerError("Can't invoke stored procedure in debug mode.");
|
1156
1157
|
}
|
1157
|
-
|
1158
|
+
const pctx = (0, context_1.getCurrentContextStore)();
|
1159
|
+
const wfid = pctx.workflowId;
|
1160
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
1158
1161
|
const $jsonCtx = {
|
1159
|
-
request:
|
1160
|
-
authenticatedUser:
|
1161
|
-
authenticatedRoles:
|
1162
|
-
assumedRole:
|
1162
|
+
request: pctx.request,
|
1163
|
+
authenticatedUser: pctx.authenticatedUser,
|
1164
|
+
authenticatedRoles: pctx.authenticatedRoles,
|
1165
|
+
assumedRole: pctx.assumedRole,
|
1163
1166
|
};
|
1164
1167
|
// TODO (Qian/Harry): remove this unshift when we remove the resultBuffer argument
|
1165
1168
|
// Note, node-pg converts JS arrays to postgres array literals, so must call JSON.strigify on
|
1166
1169
|
// args and bufferedResults before being passed to #invokeStoredProc
|
1167
|
-
const $args = [
|
1170
|
+
const $args = [wfid, funcId, pctx.presetID, $jsonCtx, null, JSON.stringify(args)];
|
1168
1171
|
const readonly = config.readOnly ?? false;
|
1169
1172
|
if (!readonly) {
|
1170
1173
|
$args.unshift(null);
|
@@ -1222,103 +1225,96 @@ class DBOSExecutor {
|
|
1222
1225
|
async external(stepFn, params, ...args) {
|
1223
1226
|
return await (await this.startStepTempWF(stepFn, params, undefined, undefined, ...args)).getResult();
|
1224
1227
|
}
|
1225
|
-
async startStepTempWF(stepFn, params,
|
1228
|
+
async startStepTempWF(stepFn, params, callerWFID, callerFunctionID, ...args) {
|
1226
1229
|
// Create a workflow and call external.
|
1227
|
-
const temp_workflow = async (
|
1228
|
-
|
1229
|
-
return await this.callStepFunction(stepFn, undefined, undefined, params.configuredInstance ?? null, ctxtImpl, ...args);
|
1230
|
+
const temp_workflow = async (...args) => {
|
1231
|
+
return await this.callStepFunction(stepFn, undefined, undefined, params.configuredInstance ?? null, ...args);
|
1230
1232
|
};
|
1231
1233
|
return await this.internalWorkflow(temp_workflow, {
|
1232
1234
|
...params,
|
1233
1235
|
tempWfType: exports.TempWorkflowType.step,
|
1234
1236
|
tempWfName: (0, decorators_1.getRegisteredMethodName)(stepFn),
|
1235
1237
|
tempWfClass: (0, decorators_1.getRegisteredMethodClassName)(stepFn),
|
1236
|
-
},
|
1238
|
+
}, callerWFID, callerFunctionID, ...args);
|
1237
1239
|
}
|
1238
1240
|
/**
|
1239
1241
|
* Execute a step function.
|
1240
1242
|
* If it encounters any error, retry according to its configured retry policy until the maximum number of attempts is reached, then throw an DBOSError.
|
1241
1243
|
* The step may execute many times, but once it is complete, it will not re-execute.
|
1242
1244
|
*/
|
1243
|
-
async callStepFunction(stepFn, stepFnName, stepConfig, clsInst,
|
1245
|
+
async callStepFunction(stepFn, stepFnName, stepConfig, clsInst, ...args) {
|
1244
1246
|
stepFnName = stepFnName ?? stepFn.name ?? '<unnamed>';
|
1245
|
-
let passContext = false;
|
1246
1247
|
if (!stepConfig) {
|
1247
1248
|
const stepReg = this.getStepInfo(stepFn);
|
1248
1249
|
stepConfig = stepReg?.config;
|
1249
|
-
passContext = stepReg?.registration.passContext ?? true;
|
1250
1250
|
}
|
1251
1251
|
if (stepConfig === undefined) {
|
1252
1252
|
throw new error_1.DBOSNotRegisteredError(stepFnName);
|
1253
1253
|
}
|
1254
|
-
|
1255
|
-
const
|
1254
|
+
const lctx = (0, context_1.getCurrentContextStore)();
|
1255
|
+
const wfid = lctx.workflowId;
|
1256
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
1257
|
+
const funcID = (0, context_1.functionIDGetIncrement)();
|
1256
1258
|
const maxRetryIntervalSec = 3600; // Maximum retry interval: 1 hour
|
1257
1259
|
const span = this.tracer.startSpan(stepFnName, {
|
1258
|
-
operationUUID:
|
1259
|
-
operationType: exports.OperationType.
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1260
|
+
operationUUID: wfid,
|
1261
|
+
operationType: exports.OperationType.STEP,
|
1262
|
+
operationName: stepFnName,
|
1263
|
+
authenticatedUser: lctx.authenticatedUser ?? '',
|
1264
|
+
assumedRole: lctx.assumedRole ?? '',
|
1265
|
+
authenticatedRoles: lctx.authenticatedRoles ?? [],
|
1263
1266
|
retriesAllowed: stepConfig.retriesAllowed,
|
1264
1267
|
intervalSeconds: stepConfig.intervalSeconds,
|
1265
1268
|
maxAttempts: stepConfig.maxAttempts,
|
1266
1269
|
backoffRate: stepConfig.backoffRate,
|
1267
|
-
},
|
1268
|
-
const ctxt = new step_1.StepContextImpl(wfCtx, funcID, span, this.logger, stepConfig, stepFnName);
|
1270
|
+
}, lctx.span);
|
1269
1271
|
// Check if this execution previously happened, returning its original result if it did.
|
1270
|
-
const checkr = await this.systemDatabase.getOperationResultAndThrowIfCancelled(
|
1272
|
+
const checkr = await this.systemDatabase.getOperationResultAndThrowIfCancelled(wfid, funcID);
|
1271
1273
|
if (checkr) {
|
1272
|
-
if (checkr.functionName !==
|
1273
|
-
throw new error_1.DBOSUnexpectedStepError(
|
1274
|
+
if (checkr.functionName !== stepFnName) {
|
1275
|
+
throw new error_1.DBOSUnexpectedStepError(wfid, funcID, stepFnName, checkr.functionName ?? '?');
|
1274
1276
|
}
|
1275
1277
|
const check = DBOSExecutor.reviveResultOrError(checkr);
|
1276
|
-
|
1277
|
-
|
1278
|
-
this.tracer.endSpan(
|
1278
|
+
span.setAttribute('cached', true);
|
1279
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1280
|
+
this.tracer.endSpan(span);
|
1279
1281
|
return check;
|
1280
1282
|
}
|
1281
|
-
if (this.
|
1282
|
-
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the step: workflow UUID: ${
|
1283
|
+
if (this.debugMode) {
|
1284
|
+
throw new error_1.DBOSDebuggerError(`Failed to find the recorded output for the step: workflow UUID: ${wfid}, step number: ${funcID}`);
|
1283
1285
|
}
|
1286
|
+
const maxAttempts = stepConfig.maxAttempts ?? 3;
|
1284
1287
|
// Execute the step function. If it throws an exception, retry with exponential backoff.
|
1285
1288
|
// After reaching the maximum number of retries, throw an DBOSError.
|
1286
1289
|
let result = exports.dbosNull;
|
1287
1290
|
let err = exports.dbosNull;
|
1288
1291
|
const errors = [];
|
1289
|
-
if (
|
1290
|
-
let
|
1291
|
-
let intervalSeconds =
|
1292
|
+
if (stepConfig.retriesAllowed) {
|
1293
|
+
let attemptNum = 0;
|
1294
|
+
let intervalSeconds = stepConfig.intervalSeconds ?? 1;
|
1292
1295
|
if (intervalSeconds > maxRetryIntervalSec) {
|
1293
1296
|
this.logger.warn(`Step config interval exceeds maximum allowed interval, capped to ${maxRetryIntervalSec} seconds!`);
|
1294
1297
|
}
|
1295
|
-
while (result === exports.dbosNull &&
|
1298
|
+
while (result === exports.dbosNull && attemptNum++ < (maxAttempts ?? 3)) {
|
1296
1299
|
try {
|
1297
|
-
await this.systemDatabase.checkIfCanceled(
|
1300
|
+
await this.systemDatabase.checkIfCanceled(wfid);
|
1298
1301
|
let cresult;
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
}
|
1304
|
-
else {
|
1305
|
-
await (0, context_1.runWithStepContext)(ctxt, numAttempts, async () => {
|
1306
|
-
const sf = stepFn;
|
1307
|
-
cresult = await sf.call(clsInst, ...args);
|
1308
|
-
});
|
1309
|
-
}
|
1302
|
+
await (0, context_1.runInStepContext)(lctx, funcID, span, maxAttempts, attemptNum, async () => {
|
1303
|
+
const sf = stepFn;
|
1304
|
+
cresult = await sf.call(clsInst, ...args);
|
1305
|
+
});
|
1310
1306
|
result = cresult;
|
1311
1307
|
}
|
1312
1308
|
catch (error) {
|
1313
1309
|
const e = error;
|
1314
1310
|
errors.push(e);
|
1315
|
-
this.logger.warn(`Error in step being automatically retried. Attempt ${
|
1316
|
-
span.addEvent(`Step attempt ${
|
1317
|
-
if (
|
1311
|
+
this.logger.warn(`Error in step being automatically retried. Attempt ${attemptNum} of ${maxAttempts}. ${e.stack}`);
|
1312
|
+
span.addEvent(`Step attempt ${attemptNum + 1} failed`, { retryIntervalSeconds: intervalSeconds, error: error.message }, performance.now());
|
1313
|
+
if (attemptNum < maxAttempts) {
|
1318
1314
|
// Sleep for an interval, then increase the interval by backoffRate.
|
1319
1315
|
// Cap at the maximum allowed retry interval.
|
1320
1316
|
await (0, utils_1.sleepms)(intervalSeconds * 1000);
|
1321
|
-
intervalSeconds *=
|
1317
|
+
intervalSeconds *= stepConfig.backoffRate ?? 2;
|
1322
1318
|
intervalSeconds = intervalSeconds < maxRetryIntervalSec ? intervalSeconds : maxRetryIntervalSec;
|
1323
1319
|
}
|
1324
1320
|
}
|
@@ -1327,17 +1323,10 @@ class DBOSExecutor {
|
|
1327
1323
|
else {
|
1328
1324
|
try {
|
1329
1325
|
let cresult;
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
}
|
1335
|
-
else {
|
1336
|
-
await (0, context_1.runWithStepContext)(ctxt, undefined, async () => {
|
1337
|
-
const sf = stepFn;
|
1338
|
-
cresult = await sf.call(clsInst, ...args);
|
1339
|
-
});
|
1340
|
-
}
|
1326
|
+
await (0, context_1.runInStepContext)(lctx, funcID, span, maxAttempts, undefined, async () => {
|
1327
|
+
const sf = stepFn;
|
1328
|
+
cresult = await sf.call(clsInst, ...args);
|
1329
|
+
});
|
1341
1330
|
result = cresult;
|
1342
1331
|
}
|
1343
1332
|
catch (error) {
|
@@ -1347,35 +1336,37 @@ class DBOSExecutor {
|
|
1347
1336
|
// `result` can only be dbosNull when the step timed out
|
1348
1337
|
if (result === exports.dbosNull) {
|
1349
1338
|
// Record the error, then throw it.
|
1350
|
-
err = err === exports.dbosNull ? new error_1.DBOSMaxStepRetriesError(stepFnName,
|
1351
|
-
await this.systemDatabase.recordOperationResult(
|
1339
|
+
err = err === exports.dbosNull ? new error_1.DBOSMaxStepRetriesError(stepFnName, maxAttempts, errors) : err;
|
1340
|
+
await this.systemDatabase.recordOperationResult(wfid, funcID, stepFnName, true, {
|
1352
1341
|
error: utils_1.DBOSJSON.stringify((0, serialize_error_1.serializeError)(err)),
|
1353
1342
|
});
|
1354
|
-
|
1355
|
-
this.tracer.endSpan(
|
1343
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err.message });
|
1344
|
+
this.tracer.endSpan(span);
|
1356
1345
|
throw err;
|
1357
1346
|
}
|
1358
1347
|
else {
|
1359
1348
|
// Record the execution and return.
|
1360
|
-
await this.systemDatabase.recordOperationResult(
|
1349
|
+
await this.systemDatabase.recordOperationResult(wfid, funcID, stepFnName, true, {
|
1361
1350
|
output: utils_1.DBOSJSON.stringify(result),
|
1362
1351
|
});
|
1363
|
-
|
1364
|
-
this.tracer.endSpan(
|
1352
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
1353
|
+
this.tracer.endSpan(span);
|
1365
1354
|
return result;
|
1366
1355
|
}
|
1367
1356
|
}
|
1368
|
-
async send(
|
1357
|
+
async send(destinationId, message, topic, idempotencyKey) {
|
1369
1358
|
// Create a workflow and call send.
|
1370
|
-
const temp_workflow = async (
|
1371
|
-
|
1359
|
+
const temp_workflow = async (destinationId, message, topic) => {
|
1360
|
+
const ctx = (0, context_1.getCurrentContextStore)();
|
1361
|
+
const functionID = (0, context_1.functionIDGetIncrement)();
|
1362
|
+
await this.systemDatabase.send(ctx.workflowId, functionID, destinationId, utils_1.DBOSJSON.stringify(message), topic);
|
1372
1363
|
};
|
1373
|
-
const workflowUUID = idempotencyKey ?
|
1364
|
+
const workflowUUID = idempotencyKey ? destinationId + idempotencyKey : undefined;
|
1374
1365
|
return (await this.workflow(temp_workflow, {
|
1375
1366
|
workflowUUID: workflowUUID,
|
1376
1367
|
tempWfType: exports.TempWorkflowType.send,
|
1377
1368
|
configuredInstance: null,
|
1378
|
-
},
|
1369
|
+
}, destinationId, message, topic)).getResult();
|
1379
1370
|
}
|
1380
1371
|
/**
|
1381
1372
|
* Wait for a workflow to emit an event, then return its value.
|
@@ -1469,7 +1460,7 @@ class DBOSExecutor {
|
|
1469
1460
|
* It runs to completion all pending workflows that were executing when the previous executor failed.
|
1470
1461
|
*/
|
1471
1462
|
async recoverPendingWorkflows(executorIDs = ['local']) {
|
1472
|
-
if (this.
|
1463
|
+
if (this.debugMode) {
|
1473
1464
|
throw new error_1.DBOSDebuggerError('Cannot recover pending workflows in debug mode.');
|
1474
1465
|
}
|
1475
1466
|
const handlerArray = [];
|
@@ -1514,14 +1505,14 @@ class DBOSExecutor {
|
|
1514
1505
|
await evtRcvr.initialize(this);
|
1515
1506
|
}
|
1516
1507
|
for (const lcl of (0, decorators_1.getLifecycleListeners)()) {
|
1517
|
-
await lcl.initialize();
|
1508
|
+
await lcl.initialize?.();
|
1518
1509
|
}
|
1519
1510
|
}
|
1520
1511
|
async deactivateEventReceivers(stopQueueThread = true) {
|
1521
1512
|
this.logger.debug('Deactivating lifecycle listeners');
|
1522
1513
|
for (const lcl of (0, decorators_1.getLifecycleListeners)()) {
|
1523
1514
|
try {
|
1524
|
-
await lcl.destroy();
|
1515
|
+
await lcl.destroy?.();
|
1525
1516
|
}
|
1526
1517
|
catch (err) {
|
1527
1518
|
const e = err;
|
@@ -1567,19 +1558,20 @@ class DBOSExecutor {
|
|
1567
1558
|
throw new error_1.DBOSError(`Failed to find inputs for workflow UUID: ${workflowID}`);
|
1568
1559
|
}
|
1569
1560
|
const inputs = utils_1.DBOSJSON.parse(wfStatus.input);
|
1570
|
-
const
|
1561
|
+
const recoverCtx = this.#getRecoveryContext(workflowID, wfStatus);
|
1571
1562
|
const { wfInfo, configuredInst } = this.getWorkflowInfoByStatus(wfStatus);
|
1572
1563
|
// If starting a new workflow, assign a new UUID. Otherwise, use the workflow's original UUID.
|
1573
1564
|
const workflowStartID = startNewWorkflow ? undefined : workflowID;
|
1574
1565
|
if (wfInfo) {
|
1575
|
-
return
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1566
|
+
return await (0, context_1.runWithTopContext)(recoverCtx, async () => {
|
1567
|
+
return await this.workflow(wfInfo.workflow, {
|
1568
|
+
workflowUUID: workflowStartID,
|
1569
|
+
configuredInstance: configuredInst,
|
1570
|
+
queueName: wfStatus.queueName,
|
1571
|
+
executeWorkflow: true,
|
1572
|
+
deadlineEpochMS: wfStatus.deadlineEpochMS,
|
1573
|
+
}, ...inputs);
|
1574
|
+
});
|
1583
1575
|
}
|
1584
1576
|
// Should be temporary workflows. Parse the name of the workflow.
|
1585
1577
|
const wfName = wfStatus.workflowName;
|
@@ -1588,20 +1580,20 @@ class DBOSExecutor {
|
|
1588
1580
|
// CB - Doesn't this happen if the user changed the function name in their code?
|
1589
1581
|
throw new error_1.DBOSError(`This should never happen! Cannot find workflow info for a non-temporary workflow! UUID ${workflowID}, name ${wfName}`);
|
1590
1582
|
}
|
1591
|
-
let temp_workflow;
|
1592
1583
|
if (nameArr[1] === exports.TempWorkflowType.transaction) {
|
1593
1584
|
const { txnInfo, clsInst } = this.getTransactionInfoByNames(wfStatus.workflowClassName, nameArr[2], wfStatus.workflowConfigName);
|
1594
1585
|
if (!txnInfo) {
|
1595
1586
|
this.logger.error(`Cannot find transaction info for UUID ${workflowID}, name ${nameArr[2]}`);
|
1596
1587
|
throw new error_1.DBOSNotRegisteredError(nameArr[2]);
|
1597
1588
|
}
|
1598
|
-
return await
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1589
|
+
return await (0, context_1.runWithTopContext)(recoverCtx, async () => {
|
1590
|
+
return await this.startTransactionTempWF(txnInfo.transaction, {
|
1591
|
+
workflowUUID: workflowStartID,
|
1592
|
+
configuredInstance: clsInst,
|
1593
|
+
queueName: wfStatus.queueName,
|
1594
|
+
executeWorkflow: true,
|
1595
|
+
}, undefined, undefined, ...inputs);
|
1596
|
+
});
|
1605
1597
|
}
|
1606
1598
|
else if (nameArr[1] === exports.TempWorkflowType.step) {
|
1607
1599
|
const { commInfo, clsInst } = this.getStepInfoByNames(wfStatus.workflowClassName, nameArr[2], wfStatus.workflowConfigName);
|
@@ -1609,25 +1601,30 @@ class DBOSExecutor {
|
|
1609
1601
|
this.logger.error(`Cannot find step info for UUID ${workflowID}, name ${nameArr[2]}`);
|
1610
1602
|
throw new error_1.DBOSNotRegisteredError(nameArr[2]);
|
1611
1603
|
}
|
1612
|
-
return await
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1604
|
+
return await (0, context_1.runWithTopContext)(recoverCtx, async () => {
|
1605
|
+
return await this.startStepTempWF(commInfo.step, {
|
1606
|
+
workflowUUID: workflowStartID,
|
1607
|
+
configuredInstance: clsInst,
|
1608
|
+
queueName: wfStatus.queueName, // Probably null
|
1609
|
+
executeWorkflow: true,
|
1610
|
+
}, undefined, undefined, ...inputs);
|
1611
|
+
});
|
1619
1612
|
}
|
1620
1613
|
else if (nameArr[1] === exports.TempWorkflowType.send) {
|
1621
|
-
|
1622
|
-
|
1614
|
+
const swf = async (destinationID, message, topic) => {
|
1615
|
+
const ctx = (0, context_1.getCurrentContextStore)();
|
1616
|
+
const functionID = (0, context_1.functionIDGetIncrement)();
|
1617
|
+
await this.systemDatabase.send(ctx.workflowId, functionID, destinationID, utils_1.DBOSJSON.stringify(message), topic);
|
1623
1618
|
};
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1619
|
+
const temp_workflow = swf;
|
1620
|
+
return await (0, context_1.runWithTopContext)(recoverCtx, async () => {
|
1621
|
+
return this.workflow(temp_workflow, {
|
1622
|
+
workflowUUID: workflowStartID,
|
1623
|
+
tempWfType: exports.TempWorkflowType.send,
|
1624
|
+
queueName: wfStatus.queueName,
|
1625
|
+
executeWorkflow: true,
|
1626
|
+
}, ...inputs);
|
1627
|
+
});
|
1631
1628
|
}
|
1632
1629
|
else {
|
1633
1630
|
this.logger.error(`Unrecognized temporary workflow! UUID ${workflowID}, name ${wfName}`);
|
@@ -1640,14 +1637,13 @@ class DBOSExecutor {
|
|
1640
1637
|
async upsertEventDispatchState(state) {
|
1641
1638
|
return await this.systemDatabase.upsertEventDispatchState(state);
|
1642
1639
|
}
|
1643
|
-
#getRecoveryContext(
|
1640
|
+
#getRecoveryContext(_workflowID, status) {
|
1644
1641
|
// Note: this doesn't inherit the original parent context's span.
|
1645
|
-
const oc =
|
1642
|
+
const oc = {};
|
1646
1643
|
oc.request = status.request;
|
1647
1644
|
oc.authenticatedUser = status.authenticatedUser;
|
1648
1645
|
oc.authenticatedRoles = status.authenticatedRoles;
|
1649
1646
|
oc.assumedRole = status.assumedRole;
|
1650
|
-
oc.workflowUUID = workflowUUID;
|
1651
1647
|
return oc;
|
1652
1648
|
}
|
1653
1649
|
async cancelWorkflow(workflowID) {
|