@dbos-inc/dbos-enterprise 5.4.1-preview → 5.5.0
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/README.md +0 -2
- package/dist/src/auto_upgrade.d.ts +7 -20
- package/dist/src/auto_upgrade.d.ts.map +1 -1
- package/dist/src/auto_upgrade.js +8 -51
- package/dist/src/auto_upgrade.js.map +1 -1
- package/dist/src/cockroach.d.ts +7 -1
- package/dist/src/cockroach.d.ts.map +1 -1
- package/dist/src/cockroach.js +7 -1
- package/dist/src/cockroach.js.map +1 -1
- package/dist/src/conductor/conductor.d.ts +8 -5
- package/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +768 -735
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/conductor/protocol.d.ts +31 -7
- package/dist/src/conductor/protocol.d.ts.map +1 -1
- package/dist/src/conductor/protocol.js +84 -17
- package/dist/src/conductor/protocol.js.map +1 -1
- package/dist/src/export.d.ts +7 -0
- package/dist/src/export.d.ts.map +1 -1
- package/dist/src/export.js +19 -24
- package/dist/src/export.js.map +1 -1
- package/dist/src/external_storage.d.ts +23 -0
- package/dist/src/external_storage.d.ts.map +1 -0
- package/dist/src/external_storage.js +278 -0
- package/dist/src/external_storage.js.map +1 -0
- package/dist/src/external_stores.d.ts +36 -0
- package/dist/src/external_stores.d.ts.map +1 -0
- package/dist/src/external_stores.js +177 -0
- package/dist/src/external_stores.js.map +1 -0
- package/dist/src/fork.d.ts +7 -0
- package/dist/src/fork.d.ts.map +1 -1
- package/dist/src/fork.js +11 -3
- package/dist/src/fork.js.map +1 -1
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +17 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/locks.d.ts +20 -0
- package/dist/src/locks.d.ts.map +1 -0
- package/dist/src/locks.js +47 -0
- package/dist/src/locks.js.map +1 -0
- package/dist/src/observability.d.ts +8 -0
- package/dist/src/observability.d.ts.map +1 -1
- package/dist/src/observability.js +69 -44
- package/dist/src/observability.js.map +1 -1
- package/dist/src/retention.d.ts +18 -34
- package/dist/src/retention.d.ts.map +1 -1
- package/dist/src/retention.js +159 -177
- package/dist/src/retention.js.map +1 -1
- package/dist/src/sharding/index.d.ts +9 -0
- package/dist/src/sharding/index.d.ts.map +1 -0
- package/dist/src/sharding/index.js +13 -0
- package/dist/src/sharding/index.js.map +1 -0
- package/dist/src/sharding/migrations.d.ts +20 -0
- package/dist/src/sharding/migrations.d.ts.map +1 -0
- package/dist/src/sharding/migrations.js +544 -0
- package/dist/src/sharding/migrations.js.map +1 -0
- package/dist/src/sharding/schema.d.ts +9 -0
- package/dist/src/sharding/schema.d.ts.map +1 -0
- package/dist/src/sharding/schema.js +12 -0
- package/dist/src/sharding/schema.js.map +1 -0
- package/dist/src/sharding/sys_db.d.ts +104 -0
- package/dist/src/sharding/sys_db.d.ts.map +1 -0
- package/dist/src/sharding/sys_db.js +776 -0
- package/dist/src/sharding/sys_db.js.map +1 -0
- package/dist/src/suspend.d.ts +24 -0
- package/dist/src/suspend.d.ts.map +1 -0
- package/dist/src/suspend.js +110 -0
- package/dist/src/suspend.js.map +1 -0
- package/package.json +17 -4
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// Copyright (c) 2026 DBOS, Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// This file is part of Transact Enterprise, proprietary software licensed under the
|
|
4
|
+
// DBOS Conductor Software License: https://www.dbos.dev/conductor-license
|
|
5
|
+
// You may not use, copy, modify, or distribute this file except as that license permits.
|
|
6
|
+
// Disabling or circumventing the entitlement checks in @dbos-inc/dbos-sdk's src/enterprise.ts
|
|
7
|
+
// that gate it is a material breach of that license.
|
|
1
8
|
"use strict";
|
|
2
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -40,9 +47,9 @@ const fork_1 = require("../fork");
|
|
|
40
47
|
const observability_1 = require("../observability");
|
|
41
48
|
const retention_1 = require("../retention");
|
|
42
49
|
const scheduler_1 = require("@dbos-inc/dbos-sdk/internal/scheduler/scheduler");
|
|
50
|
+
const shard_keys_1 = require("@dbos-inc/dbos-sdk/internal/shard_keys");
|
|
43
51
|
const gzip = (0, node_util_1.promisify)(zlib.gzip);
|
|
44
52
|
const gunzip = (0, node_util_1.promisify)(zlib.gunzip);
|
|
45
|
-
// Commands that exist only to move workflow data, refused in metadata-only mode.
|
|
46
53
|
const DATA_MESSAGE_TYPES = new Set([
|
|
47
54
|
protocol.MessageType.GET_WORKFLOW_EVENTS,
|
|
48
55
|
protocol.MessageType.GET_WORKFLOW_NOTIFICATIONS,
|
|
@@ -50,17 +57,26 @@ const DATA_MESSAGE_TYPES = new Set([
|
|
|
50
57
|
protocol.MessageType.EXPORT_WORKFLOW,
|
|
51
58
|
protocol.MessageType.IMPORT_WORKFLOW,
|
|
52
59
|
]);
|
|
60
|
+
function positionsByKey(ids, shardKeys) {
|
|
61
|
+
const groups = new Map();
|
|
62
|
+
ids.forEach((workflowID, i) => {
|
|
63
|
+
const key = shardKeys.get(workflowID);
|
|
64
|
+
const group = groups.get(key) ?? [];
|
|
65
|
+
group.push(i);
|
|
66
|
+
groups.set(key, group);
|
|
67
|
+
});
|
|
68
|
+
return Array.from(groups.values());
|
|
69
|
+
}
|
|
53
70
|
class ConductorWebsocket {
|
|
54
71
|
dbosExec;
|
|
55
72
|
url;
|
|
56
73
|
websocket = undefined;
|
|
57
|
-
isShuttingDown = false;
|
|
58
|
-
pingPeriodMs = 20000;
|
|
59
|
-
pingTimeoutMs = 15000;
|
|
60
|
-
pingIntervalTimeout = undefined;
|
|
74
|
+
isShuttingDown = false;
|
|
75
|
+
pingPeriodMs = 20000;
|
|
76
|
+
pingTimeoutMs = 15000;
|
|
77
|
+
pingIntervalTimeout = undefined;
|
|
61
78
|
reconnectDelayMs = 1000;
|
|
62
79
|
reconnectTimeout = undefined;
|
|
63
|
-
// The retention round running off the message loop, if there is one.
|
|
64
80
|
retentionRound = undefined;
|
|
65
81
|
appName;
|
|
66
82
|
executorMetadata;
|
|
@@ -77,7 +93,6 @@ class ConductorWebsocket {
|
|
|
77
93
|
start() {
|
|
78
94
|
this.dispatchLoop();
|
|
79
95
|
}
|
|
80
|
-
/** Logs a command's exception in full and returns the error message Conductor may see. */
|
|
81
96
|
reportException(context, e) {
|
|
82
97
|
const errorMsg = `${context}: ${e.message}`;
|
|
83
98
|
this.dbosExec.logger.error(errorMsg);
|
|
@@ -91,9 +106,8 @@ class ConductorWebsocket {
|
|
|
91
106
|
clearInterval(currPing?.interval);
|
|
92
107
|
clearTimeout(currPing?.timeout);
|
|
93
108
|
if (currWebsocket) {
|
|
94
|
-
currWebsocket.terminate();
|
|
109
|
+
currWebsocket.terminate();
|
|
95
110
|
if (this.websocket === currWebsocket) {
|
|
96
|
-
// Only clear if it's the same websocket
|
|
97
111
|
this.websocket = undefined;
|
|
98
112
|
}
|
|
99
113
|
}
|
|
@@ -105,23 +119,750 @@ class ConductorWebsocket {
|
|
|
105
119
|
this.dispatchLoop();
|
|
106
120
|
}, this.reconnectDelayMs);
|
|
107
121
|
}
|
|
122
|
+
async #handleMessage(currWebsocket, baseMsg, shardKeys) {
|
|
123
|
+
const msgType = baseMsg.type;
|
|
124
|
+
let errorMsg = undefined;
|
|
125
|
+
if (this.metadataOnlyMode && DATA_MESSAGE_TYPES.has(msgType)) {
|
|
126
|
+
const refusedResp = new protocol.BaseResponse(msgType, baseMsg.request_id, `${msgType} is not allowed in conductor metadata-only mode`);
|
|
127
|
+
currWebsocket.send(JSON.stringify(refusedResp));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
switch (msgType) {
|
|
131
|
+
case protocol.MessageType.EXECUTOR_INFO:
|
|
132
|
+
const infoResp = new protocol.ExecutorInfoResponse(baseMsg.request_id, utils_1.globalParams.executorID, utils_1.globalParams.appVersion, (0, node_os_1.hostname)(), 'typescript', utils_1.globalParams.dbosVersion, undefined, this.executorMetadata, this.dbosExec.systemDatabase.sharded);
|
|
133
|
+
currWebsocket.send(JSON.stringify(infoResp));
|
|
134
|
+
this.dbosExec.logger.info('Connected to DBOS conductor');
|
|
135
|
+
break;
|
|
136
|
+
case protocol.MessageType.RECOVERY:
|
|
137
|
+
const recoveryMsg = baseMsg;
|
|
138
|
+
let success = true;
|
|
139
|
+
try {
|
|
140
|
+
await this.dbosExec.recoverPendingWorkflows(recoveryMsg.executor_ids);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
errorMsg = this.reportException('Exception encountered when recovering workflows', e);
|
|
144
|
+
success = false;
|
|
145
|
+
}
|
|
146
|
+
const recoveryResp = new protocol.RecoveryResponse(baseMsg.request_id, success, errorMsg);
|
|
147
|
+
currWebsocket.send(JSON.stringify(recoveryResp));
|
|
148
|
+
break;
|
|
149
|
+
case protocol.MessageType.CANCEL:
|
|
150
|
+
const cancelMsg = baseMsg;
|
|
151
|
+
const cancelIds = cancelMsg.workflow_ids?.length ? cancelMsg.workflow_ids : [cancelMsg.workflow_id];
|
|
152
|
+
let cancelSuccess = true;
|
|
153
|
+
try {
|
|
154
|
+
for (const group of positionsByKey(cancelIds, shardKeys)) {
|
|
155
|
+
await this.dbosExec.systemDatabase.cancelWorkflows(group.map((i) => cancelIds[i]), cancelMsg.cancel_children);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
errorMsg = this.reportException(`Exception encountered when cancelling workflow(s) ${String(cancelIds)}`, e);
|
|
160
|
+
cancelSuccess = false;
|
|
161
|
+
}
|
|
162
|
+
const cancelResp = new protocol.CancelResponse(baseMsg.request_id, cancelSuccess, errorMsg);
|
|
163
|
+
currWebsocket.send(JSON.stringify(cancelResp));
|
|
164
|
+
break;
|
|
165
|
+
case protocol.MessageType.DELETE:
|
|
166
|
+
const deleteMsg = baseMsg;
|
|
167
|
+
const deleteIds = deleteMsg.workflow_ids?.length ? deleteMsg.workflow_ids : [deleteMsg.workflow_id];
|
|
168
|
+
let deleteSuccess = true;
|
|
169
|
+
try {
|
|
170
|
+
for (const group of positionsByKey(deleteIds, shardKeys)) {
|
|
171
|
+
await this.dbosExec.systemDatabase.deleteWorkflows(group.map((i) => deleteIds[i]), deleteMsg.delete_children ?? false);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
errorMsg = this.reportException(`Exception encountered when deleting workflow(s) ${String(deleteIds)}`, e);
|
|
176
|
+
deleteSuccess = false;
|
|
177
|
+
}
|
|
178
|
+
const deleteResp = new protocol.DeleteResponse(baseMsg.request_id, deleteSuccess, errorMsg);
|
|
179
|
+
currWebsocket.send(JSON.stringify(deleteResp));
|
|
180
|
+
break;
|
|
181
|
+
case protocol.MessageType.RESUME:
|
|
182
|
+
const resumeMsg = baseMsg;
|
|
183
|
+
const resumeIds = resumeMsg.workflow_ids?.length ? resumeMsg.workflow_ids : [resumeMsg.workflow_id];
|
|
184
|
+
let resumeSuccess = true;
|
|
185
|
+
try {
|
|
186
|
+
for (const group of positionsByKey(resumeIds, shardKeys)) {
|
|
187
|
+
await this.dbosExec.systemDatabase.resumeWorkflows(group.map((i) => resumeIds[i]), resumeMsg.queue_name);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
errorMsg = this.reportException(`Exception encountered when resuming workflow(s) ${String(resumeIds)}`, e);
|
|
192
|
+
resumeSuccess = false;
|
|
193
|
+
}
|
|
194
|
+
const resumeResp = new protocol.ResumeResponse(baseMsg.request_id, resumeSuccess, errorMsg);
|
|
195
|
+
currWebsocket.send(JSON.stringify(resumeResp));
|
|
196
|
+
break;
|
|
197
|
+
case protocol.MessageType.REWIND_WORKFLOW:
|
|
198
|
+
const rewindMsg = baseMsg;
|
|
199
|
+
const rewindBody = rewindMsg.body;
|
|
200
|
+
let rewindSuccess = true;
|
|
201
|
+
try {
|
|
202
|
+
await this.dbosExec.rewindWorkflow(rewindBody.workflow_id, rewindBody.start_step ?? 0, {
|
|
203
|
+
applicationVersion: rewindBody.application_version,
|
|
204
|
+
queueName: rewindBody.queue_name,
|
|
205
|
+
queuePartitionKey: rewindBody.queue_partition_key,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
errorMsg = this.reportException(`Exception encountered when rewinding workflow ${rewindBody.workflow_id} to step ${rewindBody.start_step ?? 0}`, e);
|
|
210
|
+
rewindSuccess = false;
|
|
211
|
+
}
|
|
212
|
+
const rewindResp = new protocol.RewindWorkflowResponse(baseMsg.request_id, rewindSuccess, errorMsg);
|
|
213
|
+
currWebsocket.send(JSON.stringify(rewindResp));
|
|
214
|
+
break;
|
|
215
|
+
case protocol.MessageType.FORK_WORKFLOW:
|
|
216
|
+
const forkMsg = baseMsg;
|
|
217
|
+
let newWorkflowID = forkMsg.body.new_workflow_id;
|
|
218
|
+
try {
|
|
219
|
+
newWorkflowID = await this.dbosExec.forkWorkflow(forkMsg.body.workflow_id, forkMsg.body.start_step, {
|
|
220
|
+
newWorkflowID: newWorkflowID,
|
|
221
|
+
applicationVersion: forkMsg.body.application_version,
|
|
222
|
+
queueName: forkMsg.body.queue_name,
|
|
223
|
+
queuePartitionKey: forkMsg.body.queue_partition_key,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
errorMsg = this.reportException(`Exception encountered when forking workflow ${forkMsg.body.workflow_id} to new workflow ${newWorkflowID} on step ${forkMsg.body.start_step}, app version ${forkMsg.body.application_version}`, e);
|
|
228
|
+
newWorkflowID = undefined;
|
|
229
|
+
}
|
|
230
|
+
const forkResp = new protocol.ForkWorkflowResponse(baseMsg.request_id, newWorkflowID, errorMsg);
|
|
231
|
+
currWebsocket.send(JSON.stringify(forkResp));
|
|
232
|
+
break;
|
|
233
|
+
case protocol.MessageType.FORK_FROM_FAILURE:
|
|
234
|
+
const forkFromFailureMsg = baseMsg;
|
|
235
|
+
const forkFromFailureBody = forkFromFailureMsg.body;
|
|
236
|
+
let forkedWorkflowIDs = undefined;
|
|
237
|
+
try {
|
|
238
|
+
forkedWorkflowIDs = await (0, fork_1.forkFromFailure)(this.dbosExec.systemDatabase, forkFromFailureBody.workflow_ids, {
|
|
239
|
+
applicationVersion: forkFromFailureBody.application_version,
|
|
240
|
+
queueName: forkFromFailureBody.queue_name,
|
|
241
|
+
queuePartitionKey: forkFromFailureBody.queue_partition_key,
|
|
242
|
+
fromLastFailure: forkFromFailureBody.from_last_failure ?? false,
|
|
243
|
+
fromLastStep: forkFromFailureBody.from_last_step ?? false,
|
|
244
|
+
fromStep: forkFromFailureBody.from_step,
|
|
245
|
+
fromStepName: forkFromFailureBody.from_step_name,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
errorMsg = this.reportException('Exception encountered when bulk forking workflows', e);
|
|
250
|
+
}
|
|
251
|
+
const forkFromFailureResp = new protocol.ForkFromFailureResponse(baseMsg.request_id, forkedWorkflowIDs, errorMsg);
|
|
252
|
+
currWebsocket.send(JSON.stringify(forkFromFailureResp));
|
|
253
|
+
break;
|
|
254
|
+
case protocol.MessageType.LIST_WORKFLOWS:
|
|
255
|
+
const listWFMsg = baseMsg;
|
|
256
|
+
const body = listWFMsg.body;
|
|
257
|
+
const listWFReq = {
|
|
258
|
+
workflowIDs: body.workflow_uuids,
|
|
259
|
+
shardKey: body.shard_key ?? undefined,
|
|
260
|
+
parentShardKey: body.parent_shard_key ?? undefined,
|
|
261
|
+
workflowName: body.workflow_name,
|
|
262
|
+
authenticatedUser: body.authenticated_user,
|
|
263
|
+
startTime: body.start_time,
|
|
264
|
+
endTime: body.end_time,
|
|
265
|
+
completedAfter: body.completed_after,
|
|
266
|
+
completedBefore: body.completed_before,
|
|
267
|
+
dequeuedAfter: body.dequeued_after,
|
|
268
|
+
dequeuedBefore: body.dequeued_before,
|
|
269
|
+
status: body.status,
|
|
270
|
+
applicationVersion: body.application_version,
|
|
271
|
+
forkedFrom: body.forked_from,
|
|
272
|
+
parentWorkflowID: body.parent_workflow_id,
|
|
273
|
+
queueName: body.queue_name,
|
|
274
|
+
limit: body.limit,
|
|
275
|
+
offset: body.offset,
|
|
276
|
+
sortDesc: body.sort_desc,
|
|
277
|
+
workflow_id_prefix: body.workflow_id_prefix,
|
|
278
|
+
loadInput: (body.load_input ?? false) && !this.metadataOnlyMode,
|
|
279
|
+
loadOutput: (body.load_output ?? false) && !this.metadataOnlyMode,
|
|
280
|
+
executorId: body.executor_id,
|
|
281
|
+
queuesOnly: body.queues_only,
|
|
282
|
+
wasForkedFrom: body.was_forked_from,
|
|
283
|
+
isFork: body.is_fork,
|
|
284
|
+
hasParent: body.has_parent,
|
|
285
|
+
attributes: body.attributes,
|
|
286
|
+
scheduleName: body.schedule_name,
|
|
287
|
+
applicationName: body.application_name,
|
|
288
|
+
};
|
|
289
|
+
let workflowsOutput = [];
|
|
290
|
+
try {
|
|
291
|
+
const workflows = await this.dbosExec.listWorkflows(listWFReq);
|
|
292
|
+
workflowsOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
errorMsg = this.reportException('Exception encountered when listing workflows', e);
|
|
296
|
+
}
|
|
297
|
+
const wfsResp = new protocol.ListWorkflowsResponse(listWFMsg.request_id, workflowsOutput, errorMsg);
|
|
298
|
+
currWebsocket.send(JSON.stringify(wfsResp));
|
|
299
|
+
break;
|
|
300
|
+
case protocol.MessageType.LIST_QUEUED_WORKFLOWS:
|
|
301
|
+
const listQueuedWFMsg = baseMsg;
|
|
302
|
+
const bodyQueued = listQueuedWFMsg.body;
|
|
303
|
+
const listQueuedWFReq = {
|
|
304
|
+
workflowIDs: bodyQueued.workflow_uuids,
|
|
305
|
+
shardKey: bodyQueued.shard_key ?? undefined,
|
|
306
|
+
parentShardKey: bodyQueued.parent_shard_key ?? undefined,
|
|
307
|
+
workflowName: bodyQueued.workflow_name,
|
|
308
|
+
authenticatedUser: bodyQueued.authenticated_user,
|
|
309
|
+
startTime: bodyQueued.start_time,
|
|
310
|
+
endTime: bodyQueued.end_time,
|
|
311
|
+
completedAfter: bodyQueued.completed_after,
|
|
312
|
+
completedBefore: bodyQueued.completed_before,
|
|
313
|
+
dequeuedAfter: bodyQueued.dequeued_after,
|
|
314
|
+
dequeuedBefore: bodyQueued.dequeued_before,
|
|
315
|
+
status: bodyQueued.status,
|
|
316
|
+
applicationVersion: bodyQueued.application_version,
|
|
317
|
+
forkedFrom: bodyQueued.forked_from,
|
|
318
|
+
parentWorkflowID: bodyQueued.parent_workflow_id,
|
|
319
|
+
queueName: bodyQueued.queue_name,
|
|
320
|
+
limit: bodyQueued.limit,
|
|
321
|
+
offset: bodyQueued.offset,
|
|
322
|
+
sortDesc: bodyQueued.sort_desc,
|
|
323
|
+
workflow_id_prefix: bodyQueued.workflow_id_prefix,
|
|
324
|
+
loadInput: (bodyQueued.load_input ?? false) && !this.metadataOnlyMode,
|
|
325
|
+
loadOutput: (bodyQueued.load_output ?? false) && !this.metadataOnlyMode,
|
|
326
|
+
executorId: bodyQueued.executor_id,
|
|
327
|
+
wasForkedFrom: bodyQueued.was_forked_from,
|
|
328
|
+
isFork: bodyQueued.is_fork,
|
|
329
|
+
hasParent: bodyQueued.has_parent,
|
|
330
|
+
attributes: bodyQueued.attributes,
|
|
331
|
+
scheduleName: bodyQueued.schedule_name,
|
|
332
|
+
applicationName: bodyQueued.application_name,
|
|
333
|
+
};
|
|
334
|
+
let queuedWFOutput = [];
|
|
335
|
+
try {
|
|
336
|
+
const workflows = await this.dbosExec.listQueuedWorkflows(listQueuedWFReq);
|
|
337
|
+
queuedWFOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
errorMsg = this.reportException('Exception encountered when listing queued workflows', e);
|
|
341
|
+
}
|
|
342
|
+
const queuedWfsResp = new protocol.ListQueuedWorkflowsResponse(listQueuedWFMsg.request_id, queuedWFOutput, errorMsg);
|
|
343
|
+
currWebsocket.send(JSON.stringify(queuedWfsResp));
|
|
344
|
+
break;
|
|
345
|
+
case protocol.MessageType.GET_WORKFLOW:
|
|
346
|
+
const getWFMsg = baseMsg;
|
|
347
|
+
let wfOutput = undefined;
|
|
348
|
+
try {
|
|
349
|
+
const workflows = await this.dbosExec.listWorkflows({
|
|
350
|
+
workflowIDs: [getWFMsg.workflow_id],
|
|
351
|
+
shardKey: this.dbosExec.systemDatabase.keyHint(getWFMsg.workflow_id),
|
|
352
|
+
loadInput: (getWFMsg.load_input ?? true) && !this.metadataOnlyMode,
|
|
353
|
+
loadOutput: (getWFMsg.load_output ?? true) && !this.metadataOnlyMode,
|
|
354
|
+
});
|
|
355
|
+
if (workflows.length > 0) {
|
|
356
|
+
wfOutput = new protocol.WorkflowsOutput(workflows[0]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (e) {
|
|
360
|
+
errorMsg = this.reportException(`Exception encountered when getting workflow ${getWFMsg.workflow_id}`, e);
|
|
361
|
+
}
|
|
362
|
+
const getWFResp = new protocol.GetWorkflowResponse(getWFMsg.request_id, wfOutput, errorMsg);
|
|
363
|
+
currWebsocket.send(JSON.stringify(getWFResp));
|
|
364
|
+
break;
|
|
365
|
+
case protocol.MessageType.EXIST_PENDING_WORKFLOWS:
|
|
366
|
+
const existPendingMsg = baseMsg;
|
|
367
|
+
let hasPendingWFs = false;
|
|
368
|
+
try {
|
|
369
|
+
const pendingWFs = await (0, observability_1.getPendingWorkflows)(this.dbosExec.systemDatabase, existPendingMsg.executor_id, existPendingMsg.application_version);
|
|
370
|
+
hasPendingWFs = pendingWFs.length > 0;
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
errorMsg = this.reportException('Exception encountered when checking for pending workflows', e);
|
|
374
|
+
}
|
|
375
|
+
const existPendingResp = new protocol.ExistPendingWorkflowsResponse(baseMsg.request_id, hasPendingWFs, errorMsg);
|
|
376
|
+
currWebsocket.send(JSON.stringify(existPendingResp));
|
|
377
|
+
break;
|
|
378
|
+
case protocol.MessageType.LIST_STEPS:
|
|
379
|
+
const listStepsMessage = baseMsg;
|
|
380
|
+
let workflowSteps = undefined;
|
|
381
|
+
try {
|
|
382
|
+
const stepsInfo = await this.dbosExec.listWorkflowSteps(listStepsMessage.workflow_id, (listStepsMessage.load_output ?? true) && !this.metadataOnlyMode, { limit: listStepsMessage.limit, offset: listStepsMessage.offset });
|
|
383
|
+
workflowSteps = stepsInfo?.map((i) => new protocol.WorkflowSteps(i));
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
errorMsg = this.reportException(`Exception encountered when listing steps ${listStepsMessage.workflow_id}`, e);
|
|
387
|
+
}
|
|
388
|
+
const listStepsResponse = new protocol.ListStepsResponse(listStepsMessage.request_id, workflowSteps, errorMsg);
|
|
389
|
+
currWebsocket.send(JSON.stringify(listStepsResponse));
|
|
390
|
+
break;
|
|
391
|
+
case protocol.MessageType.RETENTION:
|
|
392
|
+
const retentionMessage = baseMsg;
|
|
393
|
+
const retentionBody = retentionMessage.body;
|
|
394
|
+
let rowsThreshold = retentionBody.gc_rows_threshold;
|
|
395
|
+
if (rowsThreshold !== undefined && rowsThreshold !== null && this.dbosExec.systemDatabase.sharded) {
|
|
396
|
+
this.dbosExec.logger.warn("Ignoring Conductor's rows-based retention: a sharded system database supports only time-based retention.");
|
|
397
|
+
rowsThreshold = undefined;
|
|
398
|
+
}
|
|
399
|
+
if (this.retentionRound !== undefined) {
|
|
400
|
+
this.dbosExec.logger.warn('Skipping retention: the previous round on this executor is still running.');
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this.retentionRound = (async () => {
|
|
404
|
+
try {
|
|
405
|
+
await (0, retention_1.garbageCollect)(this.dbosExec.systemDatabase, retentionBody.gc_cutoff_epoch_ms, rowsThreshold, {
|
|
406
|
+
batchSize: retentionBody.gc_batch_size || undefined,
|
|
407
|
+
externalStores: this.dbosExec.externalStorage?.stores,
|
|
408
|
+
});
|
|
409
|
+
if (retentionBody.timeout_cutoff_epoch_ms) {
|
|
410
|
+
await (0, retention_1.globalTimeout)(this.dbosExec.systemDatabase, retentionBody.timeout_cutoff_epoch_ms);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
if (this.isShuttingDown) {
|
|
415
|
+
this.dbosExec.logger.debug('Retention interrupted by shutdown');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
this.dbosExec.logger.error(`Exception encountered during enforcing retention policy: ${e.message}`);
|
|
419
|
+
}
|
|
420
|
+
})().finally(() => {
|
|
421
|
+
this.retentionRound = undefined;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
currWebsocket.send(JSON.stringify(new protocol.RetentionResponse(retentionMessage.request_id, true)));
|
|
425
|
+
break;
|
|
426
|
+
case protocol.MessageType.GET_METRICS:
|
|
427
|
+
const getMetricsMessage = baseMsg;
|
|
428
|
+
this.dbosExec.logger.debug(`Received metrics request for time range ${getMetricsMessage.start_time} to ${getMetricsMessage.end_time}`);
|
|
429
|
+
let metricsData = [];
|
|
430
|
+
if (getMetricsMessage.metric_class === 'workflow_step_count') {
|
|
431
|
+
try {
|
|
432
|
+
const sysMetrics = await (0, observability_1.getMetrics)(this.dbosExec.systemDatabase, getMetricsMessage.start_time, getMetricsMessage.end_time, getMetricsMessage.application_name);
|
|
433
|
+
metricsData = sysMetrics.map((m) => new protocol.MetricDataOutput(m.metricType, m.metricName, m.value));
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
errorMsg = this.reportException('Exception encountered when getting metrics', e);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
errorMsg = `Unexpected metric class: ${getMetricsMessage.metric_class}`;
|
|
441
|
+
this.dbosExec.logger.warn(errorMsg);
|
|
442
|
+
}
|
|
443
|
+
const getMetricsResponse = new protocol.GetMetricsResponse(getMetricsMessage.request_id, metricsData, errorMsg);
|
|
444
|
+
currWebsocket.send(JSON.stringify(getMetricsResponse));
|
|
445
|
+
break;
|
|
446
|
+
case protocol.MessageType.EXPORT_WORKFLOW:
|
|
447
|
+
const exportMsg = baseMsg;
|
|
448
|
+
let serializedWorkflow = null;
|
|
449
|
+
try {
|
|
450
|
+
const exported = await (0, export_1.exportWorkflow)(this.dbosExec.systemDatabase, exportMsg.workflow_id, exportMsg.export_children ?? false);
|
|
451
|
+
if (exported.length > 0) {
|
|
452
|
+
const jsonStr = JSON.stringify(exported);
|
|
453
|
+
const compressed = await gzip(jsonStr);
|
|
454
|
+
serializedWorkflow = compressed.toString('base64');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
errorMsg = this.reportException(`Exception encountered when exporting workflow ${exportMsg.workflow_id}`, e);
|
|
459
|
+
}
|
|
460
|
+
const exportResp = new protocol.ExportWorkflowResponse(baseMsg.request_id, serializedWorkflow, errorMsg);
|
|
461
|
+
currWebsocket.send(JSON.stringify(exportResp));
|
|
462
|
+
break;
|
|
463
|
+
case protocol.MessageType.IMPORT_WORKFLOW:
|
|
464
|
+
const importMsg = baseMsg;
|
|
465
|
+
let importSuccess = true;
|
|
466
|
+
try {
|
|
467
|
+
const compressedData = Buffer.from(importMsg.serialized_workflow, 'base64');
|
|
468
|
+
const decompressed = await gunzip(compressedData);
|
|
469
|
+
const workflows = JSON.parse(decompressed.toString());
|
|
470
|
+
await (0, export_1.importWorkflow)(this.dbosExec.systemDatabase, workflows);
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
errorMsg = this.reportException('Exception encountered when importing workflow', e);
|
|
474
|
+
importSuccess = false;
|
|
475
|
+
}
|
|
476
|
+
const importResp = new protocol.ImportWorkflowResponse(baseMsg.request_id, importSuccess, errorMsg);
|
|
477
|
+
currWebsocket.send(JSON.stringify(importResp));
|
|
478
|
+
break;
|
|
479
|
+
case protocol.MessageType.ALERT:
|
|
480
|
+
const alertMsg = baseMsg;
|
|
481
|
+
let alertSuccess = true;
|
|
482
|
+
try {
|
|
483
|
+
const handler = (0, decorators_1.getAlertHandler)();
|
|
484
|
+
if (handler) {
|
|
485
|
+
await Promise.resolve(handler(alertMsg.name, alertMsg.message, alertMsg.metadata));
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
this.dbosExec.logger.warn(`Alert received (no handler registered): [${alertMsg.name}] ${alertMsg.message} | Metadata: ${JSON.stringify(alertMsg.metadata)}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
errorMsg = this.reportException('Exception in alert handler', e);
|
|
493
|
+
alertSuccess = false;
|
|
494
|
+
}
|
|
495
|
+
const alertResp = new protocol.AlertResponse(baseMsg.request_id, alertSuccess, errorMsg);
|
|
496
|
+
currWebsocket.send(JSON.stringify(alertResp));
|
|
497
|
+
break;
|
|
498
|
+
case protocol.MessageType.LIST_SCHEDULES:
|
|
499
|
+
const listSchedMsg = baseMsg;
|
|
500
|
+
const loadContextList = (listSchedMsg.body.load_context ?? true) && !this.metadataOnlyMode;
|
|
501
|
+
let schedOutput = [];
|
|
502
|
+
try {
|
|
503
|
+
const scheds = await this.dbosExec.systemDatabase.listSchedules({
|
|
504
|
+
status: listSchedMsg.body.status,
|
|
505
|
+
workflowName: listSchedMsg.body.workflow_name,
|
|
506
|
+
scheduleNamePrefix: listSchedMsg.body.schedule_name_prefix,
|
|
507
|
+
applicationName: listSchedMsg.body.application_name,
|
|
508
|
+
});
|
|
509
|
+
schedOutput = await Promise.all(scheds.map(async (s) => ({
|
|
510
|
+
schedule_id: s.scheduleId,
|
|
511
|
+
schedule_name: s.scheduleName,
|
|
512
|
+
workflow_name: s.workflowName,
|
|
513
|
+
workflow_class_name: s.workflowClassName || undefined,
|
|
514
|
+
schedule: s.schedule,
|
|
515
|
+
status: s.status,
|
|
516
|
+
context: loadContextList
|
|
517
|
+
? protocol.inspectForConductor(await this.dbosExec.serializer.parse(s.context))
|
|
518
|
+
: undefined,
|
|
519
|
+
last_fired_at: s.lastFiredAt,
|
|
520
|
+
automatic_backfill: s.automaticBackfill,
|
|
521
|
+
cron_timezone: s.cronTimezone,
|
|
522
|
+
queue_name: s.queueName,
|
|
523
|
+
application_name: s.applicationName ?? null,
|
|
524
|
+
shard_key: s.shardKey ?? null,
|
|
525
|
+
})));
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
errorMsg = this.reportException('Exception encountered when listing schedules', e);
|
|
529
|
+
}
|
|
530
|
+
const listSchedResp = new protocol.ListSchedulesResponse(listSchedMsg.request_id, schedOutput, errorMsg);
|
|
531
|
+
currWebsocket.send(JSON.stringify(listSchedResp));
|
|
532
|
+
break;
|
|
533
|
+
case protocol.MessageType.GET_SCHEDULE:
|
|
534
|
+
const getSchedMsg = baseMsg;
|
|
535
|
+
const loadContextGet = (getSchedMsg.load_context ?? true) && !this.metadataOnlyMode;
|
|
536
|
+
let getSchedOutput = undefined;
|
|
537
|
+
try {
|
|
538
|
+
const sched = await this.dbosExec.systemDatabase.getSchedule(getSchedMsg.schedule_name);
|
|
539
|
+
if (sched) {
|
|
540
|
+
getSchedOutput = {
|
|
541
|
+
schedule_id: sched.scheduleId,
|
|
542
|
+
schedule_name: sched.scheduleName,
|
|
543
|
+
workflow_name: sched.workflowName,
|
|
544
|
+
workflow_class_name: sched.workflowClassName || undefined,
|
|
545
|
+
schedule: sched.schedule,
|
|
546
|
+
status: sched.status,
|
|
547
|
+
context: loadContextGet
|
|
548
|
+
? protocol.inspectForConductor(await this.dbosExec.serializer.parse(sched.context))
|
|
549
|
+
: undefined,
|
|
550
|
+
last_fired_at: sched.lastFiredAt,
|
|
551
|
+
automatic_backfill: sched.automaticBackfill,
|
|
552
|
+
cron_timezone: sched.cronTimezone,
|
|
553
|
+
queue_name: sched.queueName,
|
|
554
|
+
application_name: sched.applicationName ?? null,
|
|
555
|
+
shard_key: sched.shardKey ?? null,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
errorMsg = this.reportException(`Exception encountered when getting schedule ${getSchedMsg.schedule_name}`, e);
|
|
561
|
+
}
|
|
562
|
+
const getSchedResp = new protocol.GetScheduleResponse(getSchedMsg.request_id, getSchedOutput, errorMsg);
|
|
563
|
+
currWebsocket.send(JSON.stringify(getSchedResp));
|
|
564
|
+
break;
|
|
565
|
+
case protocol.MessageType.PAUSE_SCHEDULE:
|
|
566
|
+
const pauseSchedMsg = baseMsg;
|
|
567
|
+
let pauseSuccess = true;
|
|
568
|
+
try {
|
|
569
|
+
await this.dbosExec.systemDatabase.setScheduleStatus(pauseSchedMsg.schedule_name, 'PAUSED');
|
|
570
|
+
}
|
|
571
|
+
catch (e) {
|
|
572
|
+
errorMsg = this.reportException(`Exception encountered when pausing schedule '${pauseSchedMsg.schedule_name}'`, e);
|
|
573
|
+
pauseSuccess = false;
|
|
574
|
+
}
|
|
575
|
+
const pauseSchedResp = new protocol.PauseScheduleResponse(baseMsg.request_id, pauseSuccess, errorMsg);
|
|
576
|
+
currWebsocket.send(JSON.stringify(pauseSchedResp));
|
|
577
|
+
break;
|
|
578
|
+
case protocol.MessageType.RESUME_SCHEDULE:
|
|
579
|
+
const resumeSchedMsg = baseMsg;
|
|
580
|
+
let resumeSchedSuccess = true;
|
|
581
|
+
try {
|
|
582
|
+
await this.dbosExec.systemDatabase.setScheduleStatus(resumeSchedMsg.schedule_name, 'ACTIVE');
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
errorMsg = this.reportException(`Exception encountered when resuming schedule '${resumeSchedMsg.schedule_name}'`, e);
|
|
586
|
+
resumeSchedSuccess = false;
|
|
587
|
+
}
|
|
588
|
+
const resumeSchedResp = new protocol.ResumeScheduleResponse(baseMsg.request_id, resumeSchedSuccess, errorMsg);
|
|
589
|
+
currWebsocket.send(JSON.stringify(resumeSchedResp));
|
|
590
|
+
break;
|
|
591
|
+
case protocol.MessageType.BACKFILL_SCHEDULE:
|
|
592
|
+
const backfillSchedMsg = baseMsg;
|
|
593
|
+
let backfillWorkflowIds = [];
|
|
594
|
+
try {
|
|
595
|
+
backfillWorkflowIds = await (0, scheduler_1.backfillSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, backfillSchedMsg.schedule_name, new Date(backfillSchedMsg.start), new Date(backfillSchedMsg.end));
|
|
596
|
+
}
|
|
597
|
+
catch (e) {
|
|
598
|
+
errorMsg = this.reportException(`Exception encountered when backfilling schedule '${backfillSchedMsg.schedule_name}'`, e);
|
|
599
|
+
}
|
|
600
|
+
const backfillSchedResp = new protocol.BackfillScheduleResponse(baseMsg.request_id, backfillWorkflowIds, errorMsg);
|
|
601
|
+
currWebsocket.send(JSON.stringify(backfillSchedResp));
|
|
602
|
+
break;
|
|
603
|
+
case protocol.MessageType.TRIGGER_SCHEDULE:
|
|
604
|
+
const triggerSchedMsg = baseMsg;
|
|
605
|
+
let triggerWorkflowId = undefined;
|
|
606
|
+
try {
|
|
607
|
+
triggerWorkflowId = await (0, scheduler_1.triggerSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, triggerSchedMsg.schedule_name);
|
|
608
|
+
}
|
|
609
|
+
catch (e) {
|
|
610
|
+
errorMsg = this.reportException(`Exception encountered when triggering schedule '${triggerSchedMsg.schedule_name}'`, e);
|
|
611
|
+
}
|
|
612
|
+
const triggerSchedResp = new protocol.TriggerScheduleResponse(baseMsg.request_id, triggerWorkflowId, errorMsg);
|
|
613
|
+
currWebsocket.send(JSON.stringify(triggerSchedResp));
|
|
614
|
+
break;
|
|
615
|
+
case protocol.MessageType.LIST_APPLICATION_VERSIONS:
|
|
616
|
+
let versionsOutput = [];
|
|
617
|
+
try {
|
|
618
|
+
const versions = await this.dbosExec.systemDatabase.listApplicationVersions();
|
|
619
|
+
versionsOutput = versions.map((v) => ({
|
|
620
|
+
version_id: v.versionId,
|
|
621
|
+
version_name: v.versionName,
|
|
622
|
+
version_timestamp: v.versionTimestamp,
|
|
623
|
+
created_at: v.createdAt,
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
catch (e) {
|
|
627
|
+
errorMsg = this.reportException('Exception encountered when listing application versions', e);
|
|
628
|
+
}
|
|
629
|
+
const listVersionsResp = new protocol.ListApplicationVersionsResponse(baseMsg.request_id, versionsOutput, errorMsg);
|
|
630
|
+
currWebsocket.send(JSON.stringify(listVersionsResp));
|
|
631
|
+
break;
|
|
632
|
+
case protocol.MessageType.SET_LATEST_APPLICATION_VERSION:
|
|
633
|
+
const setVersionMsg = baseMsg;
|
|
634
|
+
let setVersionSuccess = true;
|
|
635
|
+
try {
|
|
636
|
+
await this.dbosExec.systemDatabase.updateApplicationVersionTimestamp(setVersionMsg.version_name, Date.now());
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
errorMsg = this.reportException(`Exception encountered when setting latest application version '${setVersionMsg.version_name}'`, e);
|
|
640
|
+
setVersionSuccess = false;
|
|
641
|
+
}
|
|
642
|
+
const setVersionResp = new protocol.SetLatestApplicationVersionResponse(baseMsg.request_id, setVersionSuccess, errorMsg);
|
|
643
|
+
currWebsocket.send(JSON.stringify(setVersionResp));
|
|
644
|
+
break;
|
|
645
|
+
case protocol.MessageType.GET_WORKFLOW_EVENTS:
|
|
646
|
+
const eventsMsg = baseMsg;
|
|
647
|
+
let eventOutputs = undefined;
|
|
648
|
+
try {
|
|
649
|
+
const rawEvents = await (0, observability_1.getAllEvents)(this.dbosExec.systemDatabase, eventsMsg.workflow_id);
|
|
650
|
+
eventOutputs = Object.entries(rawEvents).map(([key, value]) => ({
|
|
651
|
+
key,
|
|
652
|
+
value: protocol.inspectForConductor(value),
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
catch (e) {
|
|
656
|
+
errorMsg = this.reportException(`Exception encountered when getting events for workflow ${eventsMsg.workflow_id}`, e);
|
|
657
|
+
}
|
|
658
|
+
const eventsResp = new protocol.GetWorkflowEventsResponse(baseMsg.request_id, eventOutputs, errorMsg);
|
|
659
|
+
currWebsocket.send(JSON.stringify(eventsResp));
|
|
660
|
+
break;
|
|
661
|
+
case protocol.MessageType.GET_WORKFLOW_NOTIFICATIONS:
|
|
662
|
+
const notifsMsg = baseMsg;
|
|
663
|
+
let notifOutputs = undefined;
|
|
664
|
+
try {
|
|
665
|
+
const rawNotifs = await (0, observability_1.getAllNotifications)(this.dbosExec.systemDatabase, notifsMsg.workflow_id);
|
|
666
|
+
notifOutputs = rawNotifs.map((n) => ({
|
|
667
|
+
topic: n.topic,
|
|
668
|
+
message: protocol.inspectForConductor(n.message),
|
|
669
|
+
created_at_epoch_ms: n.createdAtEpochMs,
|
|
670
|
+
consumed: n.consumed,
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
catch (e) {
|
|
674
|
+
errorMsg = this.reportException(`Exception encountered when getting notifications for workflow ${notifsMsg.workflow_id}`, e);
|
|
675
|
+
}
|
|
676
|
+
const notifsResp = new protocol.GetWorkflowNotificationsResponse(baseMsg.request_id, notifOutputs, errorMsg);
|
|
677
|
+
currWebsocket.send(JSON.stringify(notifsResp));
|
|
678
|
+
break;
|
|
679
|
+
case protocol.MessageType.GET_WORKFLOW_STREAMS:
|
|
680
|
+
const streamsMsg = baseMsg;
|
|
681
|
+
let streamOutputs = undefined;
|
|
682
|
+
try {
|
|
683
|
+
const rawStreams = await (0, observability_1.getAllStreamEntries)(this.dbosExec.systemDatabase, streamsMsg.workflow_id);
|
|
684
|
+
streamOutputs = Object.entries(rawStreams).map(([key, values]) => ({
|
|
685
|
+
key,
|
|
686
|
+
values: values.map((v) => protocol.inspectForConductor(v)),
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
catch (e) {
|
|
690
|
+
errorMsg = this.reportException(`Exception encountered when getting streams for workflow ${streamsMsg.workflow_id}`, e);
|
|
691
|
+
}
|
|
692
|
+
const streamsResp = new protocol.GetWorkflowStreamsResponse(baseMsg.request_id, streamOutputs, errorMsg);
|
|
693
|
+
currWebsocket.send(JSON.stringify(streamsResp));
|
|
694
|
+
break;
|
|
695
|
+
case protocol.MessageType.GET_WORKFLOW_AGGREGATES:
|
|
696
|
+
const aggMsg = baseMsg;
|
|
697
|
+
const aggBody = aggMsg.body;
|
|
698
|
+
let aggOutput = [];
|
|
699
|
+
let selectCount = aggBody.select_count ?? false;
|
|
700
|
+
const selectMinCreatedAt = aggBody.select_min_created_at ?? false;
|
|
701
|
+
const selectMaxQueueWaitMs = aggBody.select_max_queue_wait_ms ?? false;
|
|
702
|
+
const selectMaxTotalLatencyMs = aggBody.select_max_total_latency_ms ?? false;
|
|
703
|
+
if (!(selectCount || selectMinCreatedAt || selectMaxQueueWaitMs || selectMaxTotalLatencyMs)) {
|
|
704
|
+
selectCount = true;
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
const rows = await (0, observability_1.getWorkflowAggregates)(this.dbosExec.systemDatabase, {
|
|
708
|
+
groupByStatus: aggBody.group_by_status ?? false,
|
|
709
|
+
groupByName: aggBody.group_by_name ?? false,
|
|
710
|
+
groupByQueueName: aggBody.group_by_queue_name ?? false,
|
|
711
|
+
groupByExecutorId: aggBody.group_by_executor_id ?? false,
|
|
712
|
+
groupByApplicationVersion: aggBody.group_by_application_version ?? false,
|
|
713
|
+
groupByApplicationName: aggBody.group_by_application_name ?? false,
|
|
714
|
+
selectCount,
|
|
715
|
+
selectMinCreatedAt,
|
|
716
|
+
selectMaxQueueWaitMs,
|
|
717
|
+
selectMaxTotalLatencyMs,
|
|
718
|
+
timeBucketSizeMs: aggBody.time_bucket_size_ms ?? undefined,
|
|
719
|
+
status: aggBody.status,
|
|
720
|
+
startTime: aggBody.start_time,
|
|
721
|
+
endTime: aggBody.end_time,
|
|
722
|
+
completedAfter: aggBody.completed_after,
|
|
723
|
+
completedBefore: aggBody.completed_before,
|
|
724
|
+
dequeuedAfter: aggBody.dequeued_after,
|
|
725
|
+
dequeuedBefore: aggBody.dequeued_before,
|
|
726
|
+
name: aggBody.name,
|
|
727
|
+
appVersion: aggBody.app_version,
|
|
728
|
+
executorId: aggBody.executor_id,
|
|
729
|
+
queueName: aggBody.queue_name,
|
|
730
|
+
workflowIdPrefix: aggBody.workflow_id_prefix,
|
|
731
|
+
workflowIDs: aggBody.workflow_ids,
|
|
732
|
+
authenticatedUser: aggBody.user,
|
|
733
|
+
forkedFrom: aggBody.forked_from,
|
|
734
|
+
parentWorkflowID: aggBody.parent_workflow_id,
|
|
735
|
+
scheduleName: aggBody.schedule_name,
|
|
736
|
+
applicationName: aggBody.application_name,
|
|
737
|
+
wasForkedFrom: aggBody.was_forked_from,
|
|
738
|
+
isFork: aggBody.is_fork,
|
|
739
|
+
hasParent: aggBody.has_parent,
|
|
740
|
+
attributes: aggBody.attributes,
|
|
741
|
+
});
|
|
742
|
+
aggOutput = rows.map((r) => ({
|
|
743
|
+
group: r.group,
|
|
744
|
+
count: r.count,
|
|
745
|
+
min_created_at: r.minCreatedAt,
|
|
746
|
+
max_queue_wait_ms: r.maxQueueWaitMs,
|
|
747
|
+
max_total_latency_ms: r.maxTotalLatencyMs,
|
|
748
|
+
}));
|
|
749
|
+
}
|
|
750
|
+
catch (e) {
|
|
751
|
+
errorMsg = this.reportException('Exception encountered when getting workflow aggregates', e);
|
|
752
|
+
}
|
|
753
|
+
const aggResp = new protocol.GetWorkflowAggregatesResponse(baseMsg.request_id, aggOutput, errorMsg);
|
|
754
|
+
currWebsocket.send(JSON.stringify(aggResp));
|
|
755
|
+
break;
|
|
756
|
+
case protocol.MessageType.GET_STEP_AGGREGATES:
|
|
757
|
+
const stepAggMsg = baseMsg;
|
|
758
|
+
const stepAggBody = stepAggMsg.body;
|
|
759
|
+
let stepAggOutput = [];
|
|
760
|
+
let stepSelectCount = stepAggBody.select_count ?? false;
|
|
761
|
+
const stepSelectMaxDurationMs = stepAggBody.select_max_duration_ms ?? false;
|
|
762
|
+
if (!(stepSelectCount || stepSelectMaxDurationMs)) {
|
|
763
|
+
stepSelectCount = true;
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
const rows = await (0, observability_1.getStepAggregates)(this.dbosExec.systemDatabase, {
|
|
767
|
+
groupByFunctionName: stepAggBody.group_by_function_name ?? false,
|
|
768
|
+
groupByStatus: stepAggBody.group_by_status ?? false,
|
|
769
|
+
selectCount: stepSelectCount,
|
|
770
|
+
selectMaxDurationMs: stepSelectMaxDurationMs,
|
|
771
|
+
timeBucketSizeMs: stepAggBody.time_bucket_size_ms ?? undefined,
|
|
772
|
+
status: stepAggBody.status,
|
|
773
|
+
functionName: stepAggBody.function_name,
|
|
774
|
+
workflowIdPrefix: stepAggBody.workflow_id_prefix,
|
|
775
|
+
completedAfter: stepAggBody.completed_after,
|
|
776
|
+
completedBefore: stepAggBody.completed_before,
|
|
777
|
+
applicationName: stepAggBody.application_name,
|
|
778
|
+
});
|
|
779
|
+
stepAggOutput = rows.map((r) => ({
|
|
780
|
+
group: r.group,
|
|
781
|
+
count: r.count,
|
|
782
|
+
max_duration_ms: r.maxDurationMs,
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
catch (e) {
|
|
786
|
+
errorMsg = this.reportException('Exception encountered when getting step aggregates', e);
|
|
787
|
+
}
|
|
788
|
+
const stepAggResp = new protocol.GetStepAggregatesResponse(baseMsg.request_id, stepAggOutput, errorMsg);
|
|
789
|
+
currWebsocket.send(JSON.stringify(stepAggResp));
|
|
790
|
+
break;
|
|
791
|
+
case protocol.MessageType.LIST_QUEUES:
|
|
792
|
+
const listQueuesMsg = baseMsg;
|
|
793
|
+
let queueOutputs = [];
|
|
794
|
+
try {
|
|
795
|
+
const queues = await this.dbosExec.systemDatabase.listQueues(listQueuesMsg.body?.application_name);
|
|
796
|
+
queueOutputs = queues.map((q) => ({
|
|
797
|
+
name: q.name,
|
|
798
|
+
concurrency: q.concurrency,
|
|
799
|
+
worker_concurrency: q.workerConcurrency,
|
|
800
|
+
rate_limit_max: q.rateLimitMax,
|
|
801
|
+
rate_limit_period_sec: q.rateLimitPeriodSec,
|
|
802
|
+
priority_enabled: true,
|
|
803
|
+
partition_queue: q.partitionQueue,
|
|
804
|
+
polling_interval_sec: q.pollingIntervalSec,
|
|
805
|
+
application_name: q.applicationName ?? null,
|
|
806
|
+
partition_concurrency: q.partitionConcurrency,
|
|
807
|
+
partition_worker_concurrency: q.partitionWorkerConcurrency,
|
|
808
|
+
partition_rate_limit_max: q.partitionRateLimitMax,
|
|
809
|
+
partition_rate_limit_period_sec: q.partitionRateLimitPeriodSec,
|
|
810
|
+
}));
|
|
811
|
+
}
|
|
812
|
+
catch (e) {
|
|
813
|
+
errorMsg = this.reportException('Exception encountered when listing queues', e);
|
|
814
|
+
}
|
|
815
|
+
const listQueuesResp = new protocol.ListQueuesResponse(baseMsg.request_id, queueOutputs, errorMsg);
|
|
816
|
+
currWebsocket.send(JSON.stringify(listQueuesResp));
|
|
817
|
+
break;
|
|
818
|
+
case protocol.MessageType.GET_QUEUE:
|
|
819
|
+
const getQueueMsg = baseMsg;
|
|
820
|
+
let getQueueOutput = null;
|
|
821
|
+
try {
|
|
822
|
+
const queue = await this.dbosExec.systemDatabase.getQueue(getQueueMsg.name);
|
|
823
|
+
if (queue !== null) {
|
|
824
|
+
getQueueOutput = {
|
|
825
|
+
name: queue.name,
|
|
826
|
+
concurrency: queue.concurrency,
|
|
827
|
+
worker_concurrency: queue.workerConcurrency,
|
|
828
|
+
rate_limit_max: queue.rateLimitMax,
|
|
829
|
+
rate_limit_period_sec: queue.rateLimitPeriodSec,
|
|
830
|
+
priority_enabled: true,
|
|
831
|
+
partition_queue: queue.partitionQueue,
|
|
832
|
+
polling_interval_sec: queue.pollingIntervalSec,
|
|
833
|
+
application_name: queue.applicationName ?? null,
|
|
834
|
+
partition_concurrency: queue.partitionConcurrency,
|
|
835
|
+
partition_worker_concurrency: queue.partitionWorkerConcurrency,
|
|
836
|
+
partition_rate_limit_max: queue.partitionRateLimitMax,
|
|
837
|
+
partition_rate_limit_period_sec: queue.partitionRateLimitPeriodSec,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
catch (e) {
|
|
842
|
+
errorMsg = this.reportException(`Exception encountered when getting queue ${getQueueMsg.name}`, e);
|
|
843
|
+
}
|
|
844
|
+
const getQueueResp = new protocol.GetQueueResponse(baseMsg.request_id, getQueueOutput, errorMsg);
|
|
845
|
+
currWebsocket.send(JSON.stringify(getQueueResp));
|
|
846
|
+
break;
|
|
847
|
+
default:
|
|
848
|
+
this.dbosExec.logger.warn(`Unknown message type: ${baseMsg.type}`);
|
|
849
|
+
const unknownResp = new protocol.BaseResponse(baseMsg.type, baseMsg.request_id, 'Unknown message type');
|
|
850
|
+
currWebsocket.send(JSON.stringify(unknownResp));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
108
853
|
setPingInterval(currWebsocket, currPing) {
|
|
109
|
-
// Clear any existing ping interval to avoid multiple intervals being set
|
|
110
854
|
clearInterval(currPing.interval);
|
|
111
855
|
clearTimeout(currPing.timeout);
|
|
112
856
|
currPing.interval = setInterval(() => {
|
|
113
|
-
// Set ping timeout before sending ping. This prevents the socket from hanging at a non-open state indefinitely
|
|
114
857
|
currPing.timeout = setTimeout(() => {
|
|
115
858
|
if (this.isShuttingDown) {
|
|
116
859
|
return;
|
|
117
860
|
}
|
|
118
861
|
else if (this.reconnectTimeout === undefined) {
|
|
119
|
-
// Otherwise, try to reconnect if we haven't already
|
|
120
862
|
this.dbosExec.logger.warn('Connection to conductor lost. Reconnecting...');
|
|
121
863
|
this.resetWebsocket(currWebsocket, currPing);
|
|
122
864
|
}
|
|
123
865
|
}, this.pingTimeoutMs);
|
|
124
|
-
// Only ping if the connection is open
|
|
125
866
|
if (currWebsocket.readyState === ws_1.default.OPEN) {
|
|
126
867
|
this.dbosExec.logger.debug('Sending ping to conductor');
|
|
127
868
|
currWebsocket.ping();
|
|
@@ -144,10 +885,8 @@ class ConductorWebsocket {
|
|
|
144
885
|
}
|
|
145
886
|
try {
|
|
146
887
|
this.dbosExec.logger.debug(`Connecting to conductor at ${this.url}`);
|
|
147
|
-
// Start a new websocket connection
|
|
148
888
|
const currWebsocket = new ws_1.default(this.url, { handshakeTimeout: 5000 });
|
|
149
889
|
this.websocket = currWebsocket;
|
|
150
|
-
// Start ping interval
|
|
151
890
|
const currPing = { interval: undefined, timeout: undefined };
|
|
152
891
|
this.setPingInterval(currWebsocket, currPing);
|
|
153
892
|
this.pingIntervalTimeout = currPing;
|
|
@@ -161,724 +900,23 @@ class ConductorWebsocket {
|
|
|
161
900
|
});
|
|
162
901
|
currWebsocket.on('message', async (data) => {
|
|
163
902
|
this.dbosExec.logger.debug(`Received message from conductor: ${data}`);
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
let errorMsg = undefined;
|
|
903
|
+
const parsed = JSON.parse(data);
|
|
904
|
+
const baseMsg = parsed;
|
|
167
905
|
clearTimeout(currPing.timeout);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return;
|
|
906
|
+
let shardKeys;
|
|
907
|
+
try {
|
|
908
|
+
shardKeys = protocol.messageShardKeys(parsed);
|
|
172
909
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
let success = true;
|
|
182
|
-
try {
|
|
183
|
-
await this.dbosExec.recoverPendingWorkflows(recoveryMsg.executor_ids);
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
errorMsg = this.reportException('Exception encountered when recovering workflows', e);
|
|
187
|
-
success = false;
|
|
188
|
-
}
|
|
189
|
-
const recoveryResp = new protocol.RecoveryResponse(baseMsg.request_id, success, errorMsg);
|
|
190
|
-
currWebsocket.send(JSON.stringify(recoveryResp));
|
|
191
|
-
break;
|
|
192
|
-
case protocol.MessageType.CANCEL:
|
|
193
|
-
const cancelMsg = baseMsg;
|
|
194
|
-
const cancelIds = cancelMsg.workflow_ids ?? [cancelMsg.workflow_id];
|
|
195
|
-
let cancelSuccess = true;
|
|
196
|
-
try {
|
|
197
|
-
await this.dbosExec.systemDatabase.cancelWorkflows(cancelIds, cancelMsg.cancel_children);
|
|
198
|
-
}
|
|
199
|
-
catch (e) {
|
|
200
|
-
errorMsg = this.reportException(`Exception encountered when cancelling workflow(s) ${String(cancelIds)}`, e);
|
|
201
|
-
cancelSuccess = false;
|
|
202
|
-
}
|
|
203
|
-
const cancelResp = new protocol.CancelResponse(baseMsg.request_id, cancelSuccess, errorMsg);
|
|
204
|
-
currWebsocket.send(JSON.stringify(cancelResp));
|
|
205
|
-
break;
|
|
206
|
-
case protocol.MessageType.DELETE:
|
|
207
|
-
const deleteMsg = baseMsg;
|
|
208
|
-
const deleteIds = deleteMsg.workflow_ids ?? [deleteMsg.workflow_id];
|
|
209
|
-
let deleteSuccess = true;
|
|
210
|
-
try {
|
|
211
|
-
await this.dbosExec.systemDatabase.deleteWorkflows(deleteIds, deleteMsg.delete_children ?? false);
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
214
|
-
errorMsg = this.reportException(`Exception encountered when deleting workflow(s) ${String(deleteIds)}`, e);
|
|
215
|
-
deleteSuccess = false;
|
|
216
|
-
}
|
|
217
|
-
const deleteResp = new protocol.DeleteResponse(baseMsg.request_id, deleteSuccess, errorMsg);
|
|
218
|
-
currWebsocket.send(JSON.stringify(deleteResp));
|
|
219
|
-
break;
|
|
220
|
-
case protocol.MessageType.RESUME:
|
|
221
|
-
const resumeMsg = baseMsg;
|
|
222
|
-
const resumeIds = resumeMsg.workflow_ids ?? [resumeMsg.workflow_id];
|
|
223
|
-
let resumeSuccess = true;
|
|
224
|
-
try {
|
|
225
|
-
await this.dbosExec.systemDatabase.resumeWorkflows(resumeIds, resumeMsg.queue_name);
|
|
226
|
-
}
|
|
227
|
-
catch (e) {
|
|
228
|
-
errorMsg = this.reportException(`Exception encountered when resuming workflow(s) ${String(resumeIds)}`, e);
|
|
229
|
-
resumeSuccess = false;
|
|
230
|
-
}
|
|
231
|
-
const resumeResp = new protocol.ResumeResponse(baseMsg.request_id, resumeSuccess, errorMsg);
|
|
232
|
-
currWebsocket.send(JSON.stringify(resumeResp));
|
|
233
|
-
break;
|
|
234
|
-
case protocol.MessageType.REWIND_WORKFLOW:
|
|
235
|
-
const rewindMsg = baseMsg;
|
|
236
|
-
const rewindBody = rewindMsg.body;
|
|
237
|
-
let rewindSuccess = true;
|
|
238
|
-
try {
|
|
239
|
-
await this.dbosExec.rewindWorkflow(rewindBody.workflow_id, rewindBody.start_step ?? 0, {
|
|
240
|
-
applicationVersion: rewindBody.application_version,
|
|
241
|
-
queueName: rewindBody.queue_name,
|
|
242
|
-
queuePartitionKey: rewindBody.queue_partition_key,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
catch (e) {
|
|
246
|
-
errorMsg = this.reportException(`Exception encountered when rewinding workflow ${rewindBody.workflow_id} to step ${rewindBody.start_step ?? 0}`, e);
|
|
247
|
-
rewindSuccess = false;
|
|
248
|
-
}
|
|
249
|
-
const rewindResp = new protocol.RewindWorkflowResponse(baseMsg.request_id, rewindSuccess, errorMsg);
|
|
250
|
-
currWebsocket.send(JSON.stringify(rewindResp));
|
|
251
|
-
break;
|
|
252
|
-
case protocol.MessageType.FORK_WORKFLOW:
|
|
253
|
-
const forkMsg = baseMsg;
|
|
254
|
-
let newWorkflowID = forkMsg.body.new_workflow_id;
|
|
255
|
-
try {
|
|
256
|
-
newWorkflowID = await this.dbosExec.forkWorkflow(forkMsg.body.workflow_id, forkMsg.body.start_step, {
|
|
257
|
-
newWorkflowID: newWorkflowID,
|
|
258
|
-
applicationVersion: forkMsg.body.application_version,
|
|
259
|
-
queueName: forkMsg.body.queue_name,
|
|
260
|
-
queuePartitionKey: forkMsg.body.queue_partition_key,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
catch (e) {
|
|
264
|
-
errorMsg = this.reportException(`Exception encountered when forking workflow ${forkMsg.body.workflow_id} to new workflow ${newWorkflowID} on step ${forkMsg.body.start_step}, app version ${forkMsg.body.application_version}`, e);
|
|
265
|
-
newWorkflowID = undefined;
|
|
266
|
-
}
|
|
267
|
-
const forkResp = new protocol.ForkWorkflowResponse(baseMsg.request_id, newWorkflowID, errorMsg);
|
|
268
|
-
currWebsocket.send(JSON.stringify(forkResp));
|
|
269
|
-
break;
|
|
270
|
-
case protocol.MessageType.FORK_FROM_FAILURE:
|
|
271
|
-
const forkFromFailureMsg = baseMsg;
|
|
272
|
-
const forkFromFailureBody = forkFromFailureMsg.body;
|
|
273
|
-
let forkedWorkflowIDs = undefined;
|
|
274
|
-
try {
|
|
275
|
-
forkedWorkflowIDs = await (0, fork_1.forkFromFailure)(this.dbosExec.systemDatabase, forkFromFailureBody.workflow_ids, {
|
|
276
|
-
applicationVersion: forkFromFailureBody.application_version,
|
|
277
|
-
queueName: forkFromFailureBody.queue_name,
|
|
278
|
-
queuePartitionKey: forkFromFailureBody.queue_partition_key,
|
|
279
|
-
fromLastFailure: forkFromFailureBody.from_last_failure ?? false,
|
|
280
|
-
fromLastStep: forkFromFailureBody.from_last_step ?? false,
|
|
281
|
-
fromStep: forkFromFailureBody.from_step,
|
|
282
|
-
fromStepName: forkFromFailureBody.from_step_name,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
catch (e) {
|
|
286
|
-
errorMsg = this.reportException('Exception encountered when bulk forking workflows', e);
|
|
287
|
-
}
|
|
288
|
-
const forkFromFailureResp = new protocol.ForkFromFailureResponse(baseMsg.request_id, forkedWorkflowIDs, errorMsg);
|
|
289
|
-
currWebsocket.send(JSON.stringify(forkFromFailureResp));
|
|
290
|
-
break;
|
|
291
|
-
case protocol.MessageType.LIST_WORKFLOWS:
|
|
292
|
-
const listWFMsg = baseMsg;
|
|
293
|
-
const body = listWFMsg.body;
|
|
294
|
-
const listWFReq = {
|
|
295
|
-
workflowIDs: body.workflow_uuids,
|
|
296
|
-
workflowName: body.workflow_name,
|
|
297
|
-
authenticatedUser: body.authenticated_user,
|
|
298
|
-
startTime: body.start_time,
|
|
299
|
-
endTime: body.end_time,
|
|
300
|
-
completedAfter: body.completed_after,
|
|
301
|
-
completedBefore: body.completed_before,
|
|
302
|
-
dequeuedAfter: body.dequeued_after,
|
|
303
|
-
dequeuedBefore: body.dequeued_before,
|
|
304
|
-
status: body.status,
|
|
305
|
-
applicationVersion: body.application_version,
|
|
306
|
-
forkedFrom: body.forked_from,
|
|
307
|
-
parentWorkflowID: body.parent_workflow_id,
|
|
308
|
-
queueName: body.queue_name,
|
|
309
|
-
limit: body.limit,
|
|
310
|
-
offset: body.offset,
|
|
311
|
-
sortDesc: body.sort_desc,
|
|
312
|
-
workflow_id_prefix: body.workflow_id_prefix,
|
|
313
|
-
loadInput: (body.load_input ?? false) && !this.metadataOnlyMode, // Default to false if not provided
|
|
314
|
-
loadOutput: (body.load_output ?? false) && !this.metadataOnlyMode, // Default to false if not provided
|
|
315
|
-
executorId: body.executor_id,
|
|
316
|
-
queuesOnly: body.queues_only,
|
|
317
|
-
wasForkedFrom: body.was_forked_from,
|
|
318
|
-
isFork: body.is_fork,
|
|
319
|
-
hasParent: body.has_parent,
|
|
320
|
-
attributes: body.attributes,
|
|
321
|
-
scheduleName: body.schedule_name,
|
|
322
|
-
applicationName: body.application_name,
|
|
323
|
-
};
|
|
324
|
-
let workflowsOutput = [];
|
|
325
|
-
try {
|
|
326
|
-
const workflows = await this.dbosExec.listWorkflows(listWFReq);
|
|
327
|
-
workflowsOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
|
|
328
|
-
}
|
|
329
|
-
catch (e) {
|
|
330
|
-
errorMsg = this.reportException('Exception encountered when listing workflows', e);
|
|
331
|
-
}
|
|
332
|
-
const wfsResp = new protocol.ListWorkflowsResponse(listWFMsg.request_id, workflowsOutput, errorMsg);
|
|
333
|
-
currWebsocket.send(JSON.stringify(wfsResp));
|
|
334
|
-
break;
|
|
335
|
-
case protocol.MessageType.LIST_QUEUED_WORKFLOWS:
|
|
336
|
-
const listQueuedWFMsg = baseMsg;
|
|
337
|
-
const bodyQueued = listQueuedWFMsg.body;
|
|
338
|
-
const listQueuedWFReq = {
|
|
339
|
-
workflowIDs: bodyQueued.workflow_uuids,
|
|
340
|
-
workflowName: bodyQueued.workflow_name,
|
|
341
|
-
authenticatedUser: bodyQueued.authenticated_user,
|
|
342
|
-
startTime: bodyQueued.start_time,
|
|
343
|
-
endTime: bodyQueued.end_time,
|
|
344
|
-
completedAfter: bodyQueued.completed_after,
|
|
345
|
-
completedBefore: bodyQueued.completed_before,
|
|
346
|
-
dequeuedAfter: bodyQueued.dequeued_after,
|
|
347
|
-
dequeuedBefore: bodyQueued.dequeued_before,
|
|
348
|
-
status: bodyQueued.status,
|
|
349
|
-
applicationVersion: bodyQueued.application_version,
|
|
350
|
-
forkedFrom: bodyQueued.forked_from,
|
|
351
|
-
parentWorkflowID: bodyQueued.parent_workflow_id,
|
|
352
|
-
queueName: bodyQueued.queue_name,
|
|
353
|
-
limit: bodyQueued.limit,
|
|
354
|
-
offset: bodyQueued.offset,
|
|
355
|
-
sortDesc: bodyQueued.sort_desc,
|
|
356
|
-
workflow_id_prefix: bodyQueued.workflow_id_prefix,
|
|
357
|
-
loadInput: (bodyQueued.load_input ?? false) && !this.metadataOnlyMode, // Default to false if not provided
|
|
358
|
-
loadOutput: (bodyQueued.load_output ?? false) && !this.metadataOnlyMode, // Default to false if not provided
|
|
359
|
-
executorId: bodyQueued.executor_id,
|
|
360
|
-
wasForkedFrom: bodyQueued.was_forked_from,
|
|
361
|
-
isFork: bodyQueued.is_fork,
|
|
362
|
-
hasParent: bodyQueued.has_parent,
|
|
363
|
-
attributes: bodyQueued.attributes,
|
|
364
|
-
scheduleName: bodyQueued.schedule_name,
|
|
365
|
-
applicationName: bodyQueued.application_name,
|
|
366
|
-
};
|
|
367
|
-
let queuedWFOutput = [];
|
|
368
|
-
try {
|
|
369
|
-
const workflows = await this.dbosExec.listQueuedWorkflows(listQueuedWFReq);
|
|
370
|
-
queuedWFOutput = workflows.map((wf) => new protocol.WorkflowsOutput(wf));
|
|
371
|
-
}
|
|
372
|
-
catch (e) {
|
|
373
|
-
errorMsg = this.reportException('Exception encountered when listing queued workflows', e);
|
|
374
|
-
}
|
|
375
|
-
const queuedWfsResp = new protocol.ListQueuedWorkflowsResponse(listQueuedWFMsg.request_id, queuedWFOutput, errorMsg);
|
|
376
|
-
currWebsocket.send(JSON.stringify(queuedWfsResp));
|
|
377
|
-
break;
|
|
378
|
-
case protocol.MessageType.GET_WORKFLOW:
|
|
379
|
-
const getWFMsg = baseMsg;
|
|
380
|
-
let wfOutput = undefined;
|
|
381
|
-
try {
|
|
382
|
-
const workflows = await this.dbosExec.listWorkflows({
|
|
383
|
-
workflowIDs: [getWFMsg.workflow_id],
|
|
384
|
-
loadInput: (getWFMsg.load_input ?? true) && !this.metadataOnlyMode,
|
|
385
|
-
loadOutput: (getWFMsg.load_output ?? true) && !this.metadataOnlyMode,
|
|
386
|
-
});
|
|
387
|
-
if (workflows.length > 0) {
|
|
388
|
-
wfOutput = new protocol.WorkflowsOutput(workflows[0]);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
catch (e) {
|
|
392
|
-
errorMsg = this.reportException(`Exception encountered when getting workflow ${getWFMsg.workflow_id}`, e);
|
|
393
|
-
}
|
|
394
|
-
const getWFResp = new protocol.GetWorkflowResponse(getWFMsg.request_id, wfOutput, errorMsg);
|
|
395
|
-
currWebsocket.send(JSON.stringify(getWFResp));
|
|
396
|
-
break;
|
|
397
|
-
case protocol.MessageType.EXIST_PENDING_WORKFLOWS:
|
|
398
|
-
const existPendingMsg = baseMsg;
|
|
399
|
-
let hasPendingWFs = false;
|
|
400
|
-
try {
|
|
401
|
-
const pendingWFs = await (0, observability_1.getPendingWorkflows)(this.dbosExec.systemDatabase, existPendingMsg.executor_id, existPendingMsg.application_version);
|
|
402
|
-
hasPendingWFs = pendingWFs.length > 0;
|
|
403
|
-
}
|
|
404
|
-
catch (e) {
|
|
405
|
-
errorMsg = this.reportException('Exception encountered when checking for pending workflows', e);
|
|
406
|
-
}
|
|
407
|
-
const existPendingResp = new protocol.ExistPendingWorkflowsResponse(baseMsg.request_id, hasPendingWFs, errorMsg);
|
|
408
|
-
currWebsocket.send(JSON.stringify(existPendingResp));
|
|
409
|
-
break;
|
|
410
|
-
case protocol.MessageType.LIST_STEPS:
|
|
411
|
-
const listStepsMessage = baseMsg;
|
|
412
|
-
let workflowSteps = undefined;
|
|
413
|
-
try {
|
|
414
|
-
const stepsInfo = await this.dbosExec.listWorkflowSteps(listStepsMessage.workflow_id, (listStepsMessage.load_output ?? true) && !this.metadataOnlyMode, { limit: listStepsMessage.limit, offset: listStepsMessage.offset });
|
|
415
|
-
workflowSteps = stepsInfo?.map((i) => new protocol.WorkflowSteps(i));
|
|
416
|
-
}
|
|
417
|
-
catch (e) {
|
|
418
|
-
errorMsg = this.reportException(`Exception encountered when listing steps ${listStepsMessage.workflow_id}`, e);
|
|
419
|
-
}
|
|
420
|
-
const listStepsResponse = new protocol.ListStepsResponse(listStepsMessage.request_id, workflowSteps, errorMsg);
|
|
421
|
-
currWebsocket.send(JSON.stringify(listStepsResponse));
|
|
422
|
-
break;
|
|
423
|
-
case protocol.MessageType.RETENTION:
|
|
424
|
-
const retentionMessage = baseMsg;
|
|
425
|
-
const retentionBody = retentionMessage.body;
|
|
426
|
-
// Off the message loop: a round takes minutes, and awaiting it here stops every
|
|
427
|
-
// other command and lets the dispatch time out into a second round on another executor.
|
|
428
|
-
if (this.retentionRound !== undefined) {
|
|
429
|
-
this.dbosExec.logger.warn('Skipping retention: the previous round on this executor is still running.');
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
this.retentionRound = (async () => {
|
|
433
|
-
try {
|
|
434
|
-
await (0, retention_1.garbageCollect)(this.dbosExec.systemDatabase, retentionBody.gc_cutoff_epoch_ms, retentionBody.gc_rows_threshold,
|
|
435
|
-
// An omitted, null, or zero gc_batch_size takes the default.
|
|
436
|
-
{ batchSize: retentionBody.gc_batch_size || undefined });
|
|
437
|
-
if (retentionBody.timeout_cutoff_epoch_ms) {
|
|
438
|
-
await (0, retention_1.globalTimeout)(this.dbosExec.systemDatabase, retentionBody.timeout_cutoff_epoch_ms);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch (e) {
|
|
442
|
-
if (this.isShuttingDown) {
|
|
443
|
-
// Shutdown took the system database away mid-round; the next round resumes the work.
|
|
444
|
-
this.dbosExec.logger.debug('Retention interrupted by shutdown');
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
this.dbosExec.logger.error(`Exception encountered during enforcing retention policy: ${e.message}`);
|
|
448
|
-
}
|
|
449
|
-
})().finally(() => {
|
|
450
|
-
this.retentionRound = undefined;
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
currWebsocket.send(JSON.stringify(new protocol.RetentionResponse(retentionMessage.request_id, true)));
|
|
454
|
-
break;
|
|
455
|
-
case protocol.MessageType.GET_METRICS:
|
|
456
|
-
const getMetricsMessage = baseMsg;
|
|
457
|
-
this.dbosExec.logger.debug(`Received metrics request for time range ${getMetricsMessage.start_time} to ${getMetricsMessage.end_time}`);
|
|
458
|
-
let metricsData = [];
|
|
459
|
-
if (getMetricsMessage.metric_class === 'workflow_step_count') {
|
|
460
|
-
try {
|
|
461
|
-
const sysMetrics = await (0, observability_1.getMetrics)(this.dbosExec.systemDatabase, getMetricsMessage.start_time, getMetricsMessage.end_time, getMetricsMessage.application_name);
|
|
462
|
-
metricsData = sysMetrics.map((m) => new protocol.MetricDataOutput(m.metricType, m.metricName, m.value));
|
|
463
|
-
}
|
|
464
|
-
catch (e) {
|
|
465
|
-
errorMsg = this.reportException('Exception encountered when getting metrics', e);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
errorMsg = `Unexpected metric class: ${getMetricsMessage.metric_class}`;
|
|
470
|
-
this.dbosExec.logger.warn(errorMsg);
|
|
471
|
-
}
|
|
472
|
-
const getMetricsResponse = new protocol.GetMetricsResponse(getMetricsMessage.request_id, metricsData, errorMsg);
|
|
473
|
-
currWebsocket.send(JSON.stringify(getMetricsResponse));
|
|
474
|
-
break;
|
|
475
|
-
case protocol.MessageType.EXPORT_WORKFLOW:
|
|
476
|
-
const exportMsg = baseMsg;
|
|
477
|
-
let serializedWorkflow = null;
|
|
478
|
-
try {
|
|
479
|
-
const exported = await (0, export_1.exportWorkflow)(this.dbosExec.systemDatabase, exportMsg.workflow_id, exportMsg.export_children ?? false);
|
|
480
|
-
if (exported.length > 0) {
|
|
481
|
-
const jsonStr = JSON.stringify(exported);
|
|
482
|
-
const compressed = await gzip(jsonStr);
|
|
483
|
-
serializedWorkflow = compressed.toString('base64');
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
catch (e) {
|
|
487
|
-
errorMsg = this.reportException(`Exception encountered when exporting workflow ${exportMsg.workflow_id}`, e);
|
|
488
|
-
}
|
|
489
|
-
const exportResp = new protocol.ExportWorkflowResponse(baseMsg.request_id, serializedWorkflow, errorMsg);
|
|
490
|
-
currWebsocket.send(JSON.stringify(exportResp));
|
|
491
|
-
break;
|
|
492
|
-
case protocol.MessageType.IMPORT_WORKFLOW:
|
|
493
|
-
const importMsg = baseMsg;
|
|
494
|
-
let importSuccess = true;
|
|
495
|
-
try {
|
|
496
|
-
const compressedData = Buffer.from(importMsg.serialized_workflow, 'base64');
|
|
497
|
-
const decompressed = await gunzip(compressedData);
|
|
498
|
-
const workflows = JSON.parse(decompressed.toString());
|
|
499
|
-
await (0, export_1.importWorkflow)(this.dbosExec.systemDatabase, workflows);
|
|
500
|
-
}
|
|
501
|
-
catch (e) {
|
|
502
|
-
errorMsg = this.reportException('Exception encountered when importing workflow', e);
|
|
503
|
-
importSuccess = false;
|
|
504
|
-
}
|
|
505
|
-
const importResp = new protocol.ImportWorkflowResponse(baseMsg.request_id, importSuccess, errorMsg);
|
|
506
|
-
currWebsocket.send(JSON.stringify(importResp));
|
|
507
|
-
break;
|
|
508
|
-
case protocol.MessageType.ALERT:
|
|
509
|
-
const alertMsg = baseMsg;
|
|
510
|
-
let alertSuccess = true;
|
|
511
|
-
try {
|
|
512
|
-
const handler = (0, decorators_1.getAlertHandler)();
|
|
513
|
-
if (handler) {
|
|
514
|
-
await Promise.resolve(handler(alertMsg.name, alertMsg.message, alertMsg.metadata));
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
this.dbosExec.logger.warn(`Alert received (no handler registered): [${alertMsg.name}] ${alertMsg.message} | Metadata: ${JSON.stringify(alertMsg.metadata)}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
catch (e) {
|
|
521
|
-
errorMsg = this.reportException('Exception in alert handler', e);
|
|
522
|
-
alertSuccess = false;
|
|
523
|
-
}
|
|
524
|
-
const alertResp = new protocol.AlertResponse(baseMsg.request_id, alertSuccess, errorMsg);
|
|
525
|
-
currWebsocket.send(JSON.stringify(alertResp));
|
|
526
|
-
break;
|
|
527
|
-
case protocol.MessageType.LIST_SCHEDULES:
|
|
528
|
-
const listSchedMsg = baseMsg;
|
|
529
|
-
const loadContextList = (listSchedMsg.body.load_context ?? true) && !this.metadataOnlyMode;
|
|
530
|
-
let schedOutput = [];
|
|
531
|
-
try {
|
|
532
|
-
const scheds = await this.dbosExec.systemDatabase.listSchedules({
|
|
533
|
-
status: listSchedMsg.body.status,
|
|
534
|
-
workflowName: listSchedMsg.body.workflow_name,
|
|
535
|
-
scheduleNamePrefix: listSchedMsg.body.schedule_name_prefix,
|
|
536
|
-
applicationName: listSchedMsg.body.application_name,
|
|
537
|
-
});
|
|
538
|
-
schedOutput = await Promise.all(scheds.map(async (s) => ({
|
|
539
|
-
schedule_id: s.scheduleId,
|
|
540
|
-
schedule_name: s.scheduleName,
|
|
541
|
-
workflow_name: s.workflowName,
|
|
542
|
-
workflow_class_name: s.workflowClassName || undefined,
|
|
543
|
-
schedule: s.schedule,
|
|
544
|
-
status: s.status,
|
|
545
|
-
context: loadContextList
|
|
546
|
-
? protocol.inspectForConductor(await this.dbosExec.serializer.parse(s.context))
|
|
547
|
-
: undefined,
|
|
548
|
-
last_fired_at: s.lastFiredAt,
|
|
549
|
-
automatic_backfill: s.automaticBackfill,
|
|
550
|
-
cron_timezone: s.cronTimezone,
|
|
551
|
-
queue_name: s.queueName,
|
|
552
|
-
application_name: s.applicationName ?? null,
|
|
553
|
-
})));
|
|
554
|
-
}
|
|
555
|
-
catch (e) {
|
|
556
|
-
errorMsg = this.reportException('Exception encountered when listing schedules', e);
|
|
557
|
-
}
|
|
558
|
-
const listSchedResp = new protocol.ListSchedulesResponse(listSchedMsg.request_id, schedOutput, errorMsg);
|
|
559
|
-
currWebsocket.send(JSON.stringify(listSchedResp));
|
|
560
|
-
break;
|
|
561
|
-
case protocol.MessageType.GET_SCHEDULE:
|
|
562
|
-
const getSchedMsg = baseMsg;
|
|
563
|
-
const loadContextGet = (getSchedMsg.load_context ?? true) && !this.metadataOnlyMode;
|
|
564
|
-
let getSchedOutput = undefined;
|
|
565
|
-
try {
|
|
566
|
-
const sched = await this.dbosExec.systemDatabase.getSchedule(getSchedMsg.schedule_name);
|
|
567
|
-
if (sched) {
|
|
568
|
-
getSchedOutput = {
|
|
569
|
-
schedule_id: sched.scheduleId,
|
|
570
|
-
schedule_name: sched.scheduleName,
|
|
571
|
-
workflow_name: sched.workflowName,
|
|
572
|
-
workflow_class_name: sched.workflowClassName || undefined,
|
|
573
|
-
schedule: sched.schedule,
|
|
574
|
-
status: sched.status,
|
|
575
|
-
context: loadContextGet
|
|
576
|
-
? protocol.inspectForConductor(await this.dbosExec.serializer.parse(sched.context))
|
|
577
|
-
: undefined,
|
|
578
|
-
last_fired_at: sched.lastFiredAt,
|
|
579
|
-
automatic_backfill: sched.automaticBackfill,
|
|
580
|
-
cron_timezone: sched.cronTimezone,
|
|
581
|
-
queue_name: sched.queueName,
|
|
582
|
-
application_name: sched.applicationName ?? null,
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
catch (e) {
|
|
587
|
-
errorMsg = this.reportException(`Exception encountered when getting schedule ${getSchedMsg.schedule_name}`, e);
|
|
588
|
-
}
|
|
589
|
-
const getSchedResp = new protocol.GetScheduleResponse(getSchedMsg.request_id, getSchedOutput, errorMsg);
|
|
590
|
-
currWebsocket.send(JSON.stringify(getSchedResp));
|
|
591
|
-
break;
|
|
592
|
-
case protocol.MessageType.PAUSE_SCHEDULE:
|
|
593
|
-
const pauseSchedMsg = baseMsg;
|
|
594
|
-
let pauseSuccess = true;
|
|
595
|
-
try {
|
|
596
|
-
await this.dbosExec.systemDatabase.setScheduleStatus(pauseSchedMsg.schedule_name, 'PAUSED');
|
|
597
|
-
}
|
|
598
|
-
catch (e) {
|
|
599
|
-
errorMsg = this.reportException(`Exception encountered when pausing schedule '${pauseSchedMsg.schedule_name}'`, e);
|
|
600
|
-
pauseSuccess = false;
|
|
601
|
-
}
|
|
602
|
-
const pauseSchedResp = new protocol.PauseScheduleResponse(baseMsg.request_id, pauseSuccess, errorMsg);
|
|
603
|
-
currWebsocket.send(JSON.stringify(pauseSchedResp));
|
|
604
|
-
break;
|
|
605
|
-
case protocol.MessageType.RESUME_SCHEDULE:
|
|
606
|
-
const resumeSchedMsg = baseMsg;
|
|
607
|
-
let resumeSchedSuccess = true;
|
|
608
|
-
try {
|
|
609
|
-
await this.dbosExec.systemDatabase.setScheduleStatus(resumeSchedMsg.schedule_name, 'ACTIVE');
|
|
610
|
-
}
|
|
611
|
-
catch (e) {
|
|
612
|
-
errorMsg = this.reportException(`Exception encountered when resuming schedule '${resumeSchedMsg.schedule_name}'`, e);
|
|
613
|
-
resumeSchedSuccess = false;
|
|
614
|
-
}
|
|
615
|
-
const resumeSchedResp = new protocol.ResumeScheduleResponse(baseMsg.request_id, resumeSchedSuccess, errorMsg);
|
|
616
|
-
currWebsocket.send(JSON.stringify(resumeSchedResp));
|
|
617
|
-
break;
|
|
618
|
-
case protocol.MessageType.BACKFILL_SCHEDULE:
|
|
619
|
-
const backfillSchedMsg = baseMsg;
|
|
620
|
-
let backfillWorkflowIds = [];
|
|
621
|
-
try {
|
|
622
|
-
backfillWorkflowIds = await (0, scheduler_1.backfillSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, backfillSchedMsg.schedule_name, new Date(backfillSchedMsg.start), new Date(backfillSchedMsg.end));
|
|
623
|
-
}
|
|
624
|
-
catch (e) {
|
|
625
|
-
errorMsg = this.reportException(`Exception encountered when backfilling schedule '${backfillSchedMsg.schedule_name}'`, e);
|
|
626
|
-
}
|
|
627
|
-
const backfillSchedResp = new protocol.BackfillScheduleResponse(baseMsg.request_id, backfillWorkflowIds, errorMsg);
|
|
628
|
-
currWebsocket.send(JSON.stringify(backfillSchedResp));
|
|
629
|
-
break;
|
|
630
|
-
case protocol.MessageType.TRIGGER_SCHEDULE:
|
|
631
|
-
const triggerSchedMsg = baseMsg;
|
|
632
|
-
let triggerWorkflowId = undefined;
|
|
633
|
-
try {
|
|
634
|
-
triggerWorkflowId = await (0, scheduler_1.triggerSchedule)(this.dbosExec.systemDatabase, this.dbosExec.serializer, triggerSchedMsg.schedule_name);
|
|
635
|
-
}
|
|
636
|
-
catch (e) {
|
|
637
|
-
errorMsg = this.reportException(`Exception encountered when triggering schedule '${triggerSchedMsg.schedule_name}'`, e);
|
|
638
|
-
}
|
|
639
|
-
const triggerSchedResp = new protocol.TriggerScheduleResponse(baseMsg.request_id, triggerWorkflowId, errorMsg);
|
|
640
|
-
currWebsocket.send(JSON.stringify(triggerSchedResp));
|
|
641
|
-
break;
|
|
642
|
-
case protocol.MessageType.LIST_APPLICATION_VERSIONS:
|
|
643
|
-
let versionsOutput = [];
|
|
644
|
-
try {
|
|
645
|
-
const versions = await this.dbosExec.systemDatabase.listApplicationVersions();
|
|
646
|
-
versionsOutput = versions.map((v) => ({
|
|
647
|
-
version_id: v.versionId,
|
|
648
|
-
version_name: v.versionName,
|
|
649
|
-
version_timestamp: v.versionTimestamp,
|
|
650
|
-
created_at: v.createdAt,
|
|
651
|
-
}));
|
|
652
|
-
}
|
|
653
|
-
catch (e) {
|
|
654
|
-
errorMsg = this.reportException('Exception encountered when listing application versions', e);
|
|
655
|
-
}
|
|
656
|
-
const listVersionsResp = new protocol.ListApplicationVersionsResponse(baseMsg.request_id, versionsOutput, errorMsg);
|
|
657
|
-
currWebsocket.send(JSON.stringify(listVersionsResp));
|
|
658
|
-
break;
|
|
659
|
-
case protocol.MessageType.SET_LATEST_APPLICATION_VERSION:
|
|
660
|
-
const setVersionMsg = baseMsg;
|
|
661
|
-
let setVersionSuccess = true;
|
|
662
|
-
try {
|
|
663
|
-
await this.dbosExec.systemDatabase.updateApplicationVersionTimestamp(setVersionMsg.version_name, Date.now());
|
|
664
|
-
}
|
|
665
|
-
catch (e) {
|
|
666
|
-
errorMsg = this.reportException(`Exception encountered when setting latest application version '${setVersionMsg.version_name}'`, e);
|
|
667
|
-
setVersionSuccess = false;
|
|
668
|
-
}
|
|
669
|
-
const setVersionResp = new protocol.SetLatestApplicationVersionResponse(baseMsg.request_id, setVersionSuccess, errorMsg);
|
|
670
|
-
currWebsocket.send(JSON.stringify(setVersionResp));
|
|
671
|
-
break;
|
|
672
|
-
case protocol.MessageType.GET_WORKFLOW_EVENTS:
|
|
673
|
-
const eventsMsg = baseMsg;
|
|
674
|
-
let eventOutputs = undefined;
|
|
675
|
-
try {
|
|
676
|
-
const rawEvents = await (0, observability_1.getAllEvents)(this.dbosExec.systemDatabase, eventsMsg.workflow_id);
|
|
677
|
-
eventOutputs = Object.entries(rawEvents).map(([key, value]) => ({
|
|
678
|
-
key,
|
|
679
|
-
value: protocol.inspectForConductor(value),
|
|
680
|
-
}));
|
|
681
|
-
}
|
|
682
|
-
catch (e) {
|
|
683
|
-
errorMsg = this.reportException(`Exception encountered when getting events for workflow ${eventsMsg.workflow_id}`, e);
|
|
684
|
-
}
|
|
685
|
-
const eventsResp = new protocol.GetWorkflowEventsResponse(baseMsg.request_id, eventOutputs, errorMsg);
|
|
686
|
-
currWebsocket.send(JSON.stringify(eventsResp));
|
|
687
|
-
break;
|
|
688
|
-
case protocol.MessageType.GET_WORKFLOW_NOTIFICATIONS:
|
|
689
|
-
const notifsMsg = baseMsg;
|
|
690
|
-
let notifOutputs = undefined;
|
|
691
|
-
try {
|
|
692
|
-
const rawNotifs = await (0, observability_1.getAllNotifications)(this.dbosExec.systemDatabase, notifsMsg.workflow_id);
|
|
693
|
-
notifOutputs = rawNotifs.map((n) => ({
|
|
694
|
-
topic: n.topic,
|
|
695
|
-
message: protocol.inspectForConductor(n.message),
|
|
696
|
-
created_at_epoch_ms: n.createdAtEpochMs,
|
|
697
|
-
consumed: n.consumed,
|
|
698
|
-
}));
|
|
699
|
-
}
|
|
700
|
-
catch (e) {
|
|
701
|
-
errorMsg = this.reportException(`Exception encountered when getting notifications for workflow ${notifsMsg.workflow_id}`, e);
|
|
702
|
-
}
|
|
703
|
-
const notifsResp = new protocol.GetWorkflowNotificationsResponse(baseMsg.request_id, notifOutputs, errorMsg);
|
|
704
|
-
currWebsocket.send(JSON.stringify(notifsResp));
|
|
705
|
-
break;
|
|
706
|
-
case protocol.MessageType.GET_WORKFLOW_STREAMS:
|
|
707
|
-
const streamsMsg = baseMsg;
|
|
708
|
-
let streamOutputs = undefined;
|
|
709
|
-
try {
|
|
710
|
-
const rawStreams = await (0, observability_1.getAllStreamEntries)(this.dbosExec.systemDatabase, streamsMsg.workflow_id);
|
|
711
|
-
streamOutputs = Object.entries(rawStreams).map(([key, values]) => ({
|
|
712
|
-
key,
|
|
713
|
-
values: values.map((v) => protocol.inspectForConductor(v)),
|
|
714
|
-
}));
|
|
715
|
-
}
|
|
716
|
-
catch (e) {
|
|
717
|
-
errorMsg = this.reportException(`Exception encountered when getting streams for workflow ${streamsMsg.workflow_id}`, e);
|
|
718
|
-
}
|
|
719
|
-
const streamsResp = new protocol.GetWorkflowStreamsResponse(baseMsg.request_id, streamOutputs, errorMsg);
|
|
720
|
-
currWebsocket.send(JSON.stringify(streamsResp));
|
|
721
|
-
break;
|
|
722
|
-
case protocol.MessageType.GET_WORKFLOW_AGGREGATES:
|
|
723
|
-
const aggMsg = baseMsg;
|
|
724
|
-
const aggBody = aggMsg.body;
|
|
725
|
-
let aggOutput = [];
|
|
726
|
-
// Backwards compat: older clients send no select_* flags and expect counts.
|
|
727
|
-
let selectCount = aggBody.select_count ?? false;
|
|
728
|
-
const selectMinCreatedAt = aggBody.select_min_created_at ?? false;
|
|
729
|
-
const selectMaxQueueWaitMs = aggBody.select_max_queue_wait_ms ?? false;
|
|
730
|
-
const selectMaxTotalLatencyMs = aggBody.select_max_total_latency_ms ?? false;
|
|
731
|
-
if (!(selectCount || selectMinCreatedAt || selectMaxQueueWaitMs || selectMaxTotalLatencyMs)) {
|
|
732
|
-
selectCount = true;
|
|
733
|
-
}
|
|
734
|
-
try {
|
|
735
|
-
const rows = await (0, observability_1.getWorkflowAggregates)(this.dbosExec.systemDatabase, {
|
|
736
|
-
groupByStatus: aggBody.group_by_status ?? false,
|
|
737
|
-
groupByName: aggBody.group_by_name ?? false,
|
|
738
|
-
groupByQueueName: aggBody.group_by_queue_name ?? false,
|
|
739
|
-
groupByExecutorId: aggBody.group_by_executor_id ?? false,
|
|
740
|
-
groupByApplicationVersion: aggBody.group_by_application_version ?? false,
|
|
741
|
-
groupByApplicationName: aggBody.group_by_application_name ?? false,
|
|
742
|
-
selectCount,
|
|
743
|
-
selectMinCreatedAt,
|
|
744
|
-
selectMaxQueueWaitMs,
|
|
745
|
-
selectMaxTotalLatencyMs,
|
|
746
|
-
timeBucketSizeMs: aggBody.time_bucket_size_ms ?? undefined,
|
|
747
|
-
status: aggBody.status,
|
|
748
|
-
startTime: aggBody.start_time,
|
|
749
|
-
endTime: aggBody.end_time,
|
|
750
|
-
completedAfter: aggBody.completed_after,
|
|
751
|
-
completedBefore: aggBody.completed_before,
|
|
752
|
-
dequeuedAfter: aggBody.dequeued_after,
|
|
753
|
-
dequeuedBefore: aggBody.dequeued_before,
|
|
754
|
-
name: aggBody.name,
|
|
755
|
-
appVersion: aggBody.app_version,
|
|
756
|
-
executorId: aggBody.executor_id,
|
|
757
|
-
queueName: aggBody.queue_name,
|
|
758
|
-
workflowIdPrefix: aggBody.workflow_id_prefix,
|
|
759
|
-
workflowIDs: aggBody.workflow_ids,
|
|
760
|
-
authenticatedUser: aggBody.user,
|
|
761
|
-
forkedFrom: aggBody.forked_from,
|
|
762
|
-
parentWorkflowID: aggBody.parent_workflow_id,
|
|
763
|
-
scheduleName: aggBody.schedule_name,
|
|
764
|
-
applicationName: aggBody.application_name,
|
|
765
|
-
wasForkedFrom: aggBody.was_forked_from,
|
|
766
|
-
isFork: aggBody.is_fork,
|
|
767
|
-
hasParent: aggBody.has_parent,
|
|
768
|
-
attributes: aggBody.attributes,
|
|
769
|
-
});
|
|
770
|
-
aggOutput = rows.map((r) => ({
|
|
771
|
-
group: r.group,
|
|
772
|
-
count: r.count,
|
|
773
|
-
min_created_at: r.minCreatedAt,
|
|
774
|
-
max_queue_wait_ms: r.maxQueueWaitMs,
|
|
775
|
-
max_total_latency_ms: r.maxTotalLatencyMs,
|
|
776
|
-
}));
|
|
777
|
-
}
|
|
778
|
-
catch (e) {
|
|
779
|
-
errorMsg = this.reportException('Exception encountered when getting workflow aggregates', e);
|
|
780
|
-
}
|
|
781
|
-
const aggResp = new protocol.GetWorkflowAggregatesResponse(baseMsg.request_id, aggOutput, errorMsg);
|
|
782
|
-
currWebsocket.send(JSON.stringify(aggResp));
|
|
783
|
-
break;
|
|
784
|
-
case protocol.MessageType.GET_STEP_AGGREGATES:
|
|
785
|
-
const stepAggMsg = baseMsg;
|
|
786
|
-
const stepAggBody = stepAggMsg.body;
|
|
787
|
-
let stepAggOutput = [];
|
|
788
|
-
// Backwards compat: older clients send no select_* flags and expect counts.
|
|
789
|
-
let stepSelectCount = stepAggBody.select_count ?? false;
|
|
790
|
-
const stepSelectMaxDurationMs = stepAggBody.select_max_duration_ms ?? false;
|
|
791
|
-
if (!(stepSelectCount || stepSelectMaxDurationMs)) {
|
|
792
|
-
stepSelectCount = true;
|
|
793
|
-
}
|
|
794
|
-
try {
|
|
795
|
-
const rows = await (0, observability_1.getStepAggregates)(this.dbosExec.systemDatabase, {
|
|
796
|
-
groupByFunctionName: stepAggBody.group_by_function_name ?? false,
|
|
797
|
-
groupByStatus: stepAggBody.group_by_status ?? false,
|
|
798
|
-
selectCount: stepSelectCount,
|
|
799
|
-
selectMaxDurationMs: stepSelectMaxDurationMs,
|
|
800
|
-
timeBucketSizeMs: stepAggBody.time_bucket_size_ms ?? undefined,
|
|
801
|
-
status: stepAggBody.status,
|
|
802
|
-
functionName: stepAggBody.function_name,
|
|
803
|
-
workflowIdPrefix: stepAggBody.workflow_id_prefix,
|
|
804
|
-
completedAfter: stepAggBody.completed_after,
|
|
805
|
-
completedBefore: stepAggBody.completed_before,
|
|
806
|
-
applicationName: stepAggBody.application_name,
|
|
807
|
-
});
|
|
808
|
-
stepAggOutput = rows.map((r) => ({
|
|
809
|
-
group: r.group,
|
|
810
|
-
count: r.count,
|
|
811
|
-
max_duration_ms: r.maxDurationMs,
|
|
812
|
-
}));
|
|
813
|
-
}
|
|
814
|
-
catch (e) {
|
|
815
|
-
errorMsg = this.reportException('Exception encountered when getting step aggregates', e);
|
|
816
|
-
}
|
|
817
|
-
const stepAggResp = new protocol.GetStepAggregatesResponse(baseMsg.request_id, stepAggOutput, errorMsg);
|
|
818
|
-
currWebsocket.send(JSON.stringify(stepAggResp));
|
|
819
|
-
break;
|
|
820
|
-
case protocol.MessageType.LIST_QUEUES:
|
|
821
|
-
const listQueuesMsg = baseMsg;
|
|
822
|
-
let queueOutputs = [];
|
|
823
|
-
try {
|
|
824
|
-
const queues = await this.dbosExec.systemDatabase.listQueues(listQueuesMsg.body?.application_name);
|
|
825
|
-
queueOutputs = queues.map((q) => ({
|
|
826
|
-
name: q.name,
|
|
827
|
-
concurrency: q.concurrency,
|
|
828
|
-
worker_concurrency: q.workerConcurrency,
|
|
829
|
-
rate_limit_max: q.rateLimitMax,
|
|
830
|
-
rate_limit_period_sec: q.rateLimitPeriodSec,
|
|
831
|
-
priority_enabled: true,
|
|
832
|
-
partition_queue: q.partitionQueue,
|
|
833
|
-
polling_interval_sec: q.pollingIntervalSec,
|
|
834
|
-
application_name: q.applicationName ?? null,
|
|
835
|
-
partition_concurrency: q.partitionConcurrency,
|
|
836
|
-
partition_worker_concurrency: q.partitionWorkerConcurrency,
|
|
837
|
-
partition_rate_limit_max: q.partitionRateLimitMax,
|
|
838
|
-
partition_rate_limit_period_sec: q.partitionRateLimitPeriodSec,
|
|
839
|
-
}));
|
|
840
|
-
}
|
|
841
|
-
catch (e) {
|
|
842
|
-
errorMsg = this.reportException('Exception encountered when listing queues', e);
|
|
843
|
-
}
|
|
844
|
-
const listQueuesResp = new protocol.ListQueuesResponse(baseMsg.request_id, queueOutputs, errorMsg);
|
|
845
|
-
currWebsocket.send(JSON.stringify(listQueuesResp));
|
|
846
|
-
break;
|
|
847
|
-
case protocol.MessageType.GET_QUEUE:
|
|
848
|
-
const getQueueMsg = baseMsg;
|
|
849
|
-
let getQueueOutput = null;
|
|
850
|
-
try {
|
|
851
|
-
const queue = await this.dbosExec.systemDatabase.getQueue(getQueueMsg.name);
|
|
852
|
-
if (queue !== null) {
|
|
853
|
-
getQueueOutput = {
|
|
854
|
-
name: queue.name,
|
|
855
|
-
concurrency: queue.concurrency,
|
|
856
|
-
worker_concurrency: queue.workerConcurrency,
|
|
857
|
-
rate_limit_max: queue.rateLimitMax,
|
|
858
|
-
rate_limit_period_sec: queue.rateLimitPeriodSec,
|
|
859
|
-
priority_enabled: true,
|
|
860
|
-
partition_queue: queue.partitionQueue,
|
|
861
|
-
polling_interval_sec: queue.pollingIntervalSec,
|
|
862
|
-
application_name: queue.applicationName ?? null,
|
|
863
|
-
partition_concurrency: queue.partitionConcurrency,
|
|
864
|
-
partition_worker_concurrency: queue.partitionWorkerConcurrency,
|
|
865
|
-
partition_rate_limit_max: queue.partitionRateLimitMax,
|
|
866
|
-
partition_rate_limit_period_sec: queue.partitionRateLimitPeriodSec,
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
catch (e) {
|
|
871
|
-
errorMsg = this.reportException(`Exception encountered when getting queue ${getQueueMsg.name}`, e);
|
|
872
|
-
}
|
|
873
|
-
const getQueueResp = new protocol.GetQueueResponse(baseMsg.request_id, getQueueOutput, errorMsg);
|
|
874
|
-
currWebsocket.send(JSON.stringify(getQueueResp));
|
|
875
|
-
break;
|
|
876
|
-
default:
|
|
877
|
-
this.dbosExec.logger.warn(`Unknown message type: ${baseMsg.type}`);
|
|
878
|
-
// Still need to send a response to the conductor
|
|
879
|
-
const unknownResp = new protocol.BaseResponse(baseMsg.type, baseMsg.request_id, 'Unknown message type');
|
|
880
|
-
currWebsocket.send(JSON.stringify(unknownResp));
|
|
910
|
+
catch (e) {
|
|
911
|
+
if (e instanceof protocol.ShardKeyMismatchError) {
|
|
912
|
+
currWebsocket.send(JSON.stringify(new protocol.BaseResponse(baseMsg.type, baseMsg.request_id, e.message)));
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
this.dbosExec.logger.warn(`Could not read the shard keys of a message from Conductor: ${String(e)}`);
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
881
918
|
}
|
|
919
|
+
await (0, shard_keys_1.bindShardKeys)(shardKeys, () => this.#handleMessage(currWebsocket, baseMsg, shardKeys));
|
|
882
920
|
});
|
|
883
921
|
currWebsocket.on('close', () => {
|
|
884
922
|
if (this.isShuttingDown) {
|
|
@@ -915,10 +953,6 @@ class ConductorWebsocket {
|
|
|
915
953
|
}
|
|
916
954
|
}
|
|
917
955
|
}
|
|
918
|
-
/**
|
|
919
|
-
* Give a round in flight a bounded chance to finish before shutdown takes the system
|
|
920
|
-
* database away from it.
|
|
921
|
-
*/
|
|
922
956
|
async awaitRetention(timeoutMs = 10000) {
|
|
923
957
|
const round = this.retentionRound;
|
|
924
958
|
if (round === undefined) {
|
|
@@ -940,7 +974,6 @@ class ConductorWebsocket {
|
|
|
940
974
|
if (this.websocket) {
|
|
941
975
|
this.websocket.close();
|
|
942
976
|
}
|
|
943
|
-
// Grace only: a round still running past this is cut when the system database is destroyed.
|
|
944
977
|
await this.awaitRetention();
|
|
945
978
|
}
|
|
946
979
|
}
|