@nocobase/plugin-workflow 2.1.0-beta.36 → 2.1.0-beta.38
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/client/618.19af7f84261c815d.js +10 -0
- package/dist/client/964.ffbf5b47ed12bbdc.js +10 -0
- package/dist/client/Branch.d.ts +7 -3
- package/dist/client/BranchContext.d.ts +18 -0
- package/dist/client/components/TimeoutInput.d.ts +11 -0
- package/dist/client/constants.d.ts +13 -0
- package/dist/client/index.js +1 -1
- package/dist/client/nodes/index.d.ts +2 -0
- package/dist/client/schemas/executions.d.ts +42 -0
- package/dist/client/utils.d.ts +17 -0
- package/dist/common/collections/executions.d.ts +42 -0
- package/dist/common/collections/executions.js +50 -1
- package/dist/common/collections/workflows.d.ts +6 -2
- package/dist/common/collections/workflows.js +3 -1
- package/dist/common/constants.d.ts +5 -0
- package/dist/common/constants.js +7 -0
- package/dist/externalVersion.js +12 -12
- package/dist/locale/de-DE.json +4 -0
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/es-ES.json +4 -0
- package/dist/locale/fr-FR.json +4 -0
- package/dist/locale/hu-HU.json +7 -3
- package/dist/locale/id-ID.json +4 -0
- package/dist/locale/it-IT.json +4 -0
- package/dist/locale/ja-JP.json +5 -1
- package/dist/locale/ko-KR.json +4 -0
- package/dist/locale/nl-NL.json +7 -3
- package/dist/locale/pt-BR.json +4 -0
- package/dist/locale/ru-RU.json +4 -0
- package/dist/locale/tr-TR.json +4 -0
- package/dist/locale/uk-UA.json +7 -3
- package/dist/locale/vi-VN.json +7 -3
- package/dist/locale/zh-CN.json +8 -0
- package/dist/locale/zh-TW.json +7 -3
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/lru-cache/package.json +1 -1
- package/dist/node_modules/nodejs-snowflake/package.json +1 -1
- package/dist/server/Dispatcher.d.ts +9 -7
- package/dist/server/Dispatcher.js +190 -122
- package/dist/server/ExecutionTimeoutManager.d.ts +45 -0
- package/dist/server/ExecutionTimeoutManager.js +312 -0
- package/dist/server/Plugin.d.ts +12 -0
- package/dist/server/Plugin.js +21 -1
- package/dist/server/Processor.d.ts +65 -9
- package/dist/server/Processor.js +285 -33
- package/dist/server/RunningExecutionRegistry.d.ts +18 -0
- package/dist/server/RunningExecutionRegistry.js +48 -0
- package/dist/server/actions/executions.d.ts +4 -3
- package/dist/server/actions/executions.js +42 -21
- package/dist/server/actions/jobs.d.ts +2 -1
- package/dist/server/actions/jobs.js +28 -1
- package/dist/server/constants.d.ts +2 -0
- package/dist/server/constants.js +3 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/instructions/index.d.ts +10 -3
- package/dist/server/migrations/20260501120000-workflow-timeout.d.ts +13 -0
- package/dist/server/migrations/20260501120000-workflow-timeout.js +63 -0
- package/dist/server/timeout-errors.d.ts +13 -0
- package/dist/server/timeout-errors.js +47 -0
- package/dist/server/types/Execution.d.ts +6 -0
- package/dist/server/types/Job.d.ts +3 -3
- package/dist/server/types/Workflow.d.ts +6 -1
- package/dist/server/utils.d.ts +11 -1
- package/dist/server/utils.js +92 -5
- package/dist/swagger/index.d.ts +22 -0
- package/dist/swagger/index.js +22 -0
- package/package.json +2 -2
- package/dist/client/261.7722d7400942730e.js +0 -10
- package/dist/client/964.6251d37b35710747.js +0 -10
|
@@ -33,18 +33,19 @@ var import_node_crypto = require("node:crypto");
|
|
|
33
33
|
var import_sequelize = require("sequelize");
|
|
34
34
|
var import_constants = require("./constants");
|
|
35
35
|
var import_Plugin = require("./Plugin");
|
|
36
|
+
var import_utils = require("./utils");
|
|
36
37
|
class Dispatcher {
|
|
37
38
|
constructor(plugin) {
|
|
38
39
|
this.plugin = plugin;
|
|
39
40
|
}
|
|
40
41
|
ready = false;
|
|
41
42
|
executing = null;
|
|
42
|
-
|
|
43
|
+
saving = null;
|
|
43
44
|
pending = [];
|
|
44
45
|
events = [];
|
|
45
46
|
eventsCount = 0;
|
|
46
47
|
get idle() {
|
|
47
|
-
return this.ready && !this.executing && !this.
|
|
48
|
+
return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
|
|
48
49
|
}
|
|
49
50
|
onQueueExecution = async (event) => {
|
|
50
51
|
const ExecutionRepo = this.plugin.db.getRepository("executions");
|
|
@@ -52,11 +53,10 @@ class Dispatcher {
|
|
|
52
53
|
filterByTk: event.executionId
|
|
53
54
|
});
|
|
54
55
|
if (!execution || execution.dispatched) {
|
|
55
|
-
this.plugin.getLogger("dispatcher").info(`execution (${event.executionId}) from queue not found or not in queueing status, skip`);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
|
|
59
|
-
this.run({ execution });
|
|
59
|
+
await this.run({ execution });
|
|
60
60
|
};
|
|
61
61
|
setReady(ready) {
|
|
62
62
|
this.ready = ready;
|
|
@@ -96,13 +96,13 @@ class Dispatcher {
|
|
|
96
96
|
this.eventsCount = this.events.length;
|
|
97
97
|
logger.info(`new event triggered, now events: ${this.events.length}`);
|
|
98
98
|
logger.debug(`event data:`, { context });
|
|
99
|
-
this.
|
|
99
|
+
this.saveEvent();
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
if (this.
|
|
101
|
+
saveEvent() {
|
|
102
|
+
if (this.saving) {
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
this.
|
|
105
|
+
this.saving = (async () => {
|
|
106
106
|
try {
|
|
107
107
|
while (this.events.length) {
|
|
108
108
|
if (this.executing && this.plugin.db.options.dialect === "sqlite") {
|
|
@@ -115,7 +115,7 @@ class Dispatcher {
|
|
|
115
115
|
logger.info(`preparing execution for event`);
|
|
116
116
|
try {
|
|
117
117
|
const execution = await this.createExecution(...event);
|
|
118
|
-
if (!
|
|
118
|
+
if (!execution.dispatched) {
|
|
119
119
|
if (this.plugin.serving() && !this.executing && !this.pending.length) {
|
|
120
120
|
logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
|
|
121
121
|
this.pending.push({ execution });
|
|
@@ -137,9 +137,9 @@ class Dispatcher {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
} finally {
|
|
140
|
-
this.
|
|
140
|
+
this.saving = null;
|
|
141
141
|
if (this.events.length) {
|
|
142
|
-
this.
|
|
142
|
+
this.saveEvent();
|
|
143
143
|
} else {
|
|
144
144
|
this.dispatch();
|
|
145
145
|
}
|
|
@@ -152,27 +152,27 @@ class Dispatcher {
|
|
|
152
152
|
execution = await job.getExecution();
|
|
153
153
|
}
|
|
154
154
|
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
|
|
155
|
-
this.run({ execution, job
|
|
155
|
+
await this.run({ execution, job });
|
|
156
156
|
}
|
|
157
157
|
async start(execution) {
|
|
158
158
|
if (execution.status) {
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
|
|
162
|
-
this.run({ execution
|
|
162
|
+
await this.run({ execution });
|
|
163
163
|
}
|
|
164
164
|
async beforeStop() {
|
|
165
165
|
this.ready = false;
|
|
166
166
|
this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
|
|
167
|
-
while (this.
|
|
168
|
-
if (this.
|
|
169
|
-
await this.
|
|
167
|
+
while (this.saving || this.executing || this.events.length || this.pending.length) {
|
|
168
|
+
if (this.saving) {
|
|
169
|
+
await this.saving;
|
|
170
170
|
}
|
|
171
171
|
if (this.executing) {
|
|
172
172
|
await this.executing;
|
|
173
173
|
}
|
|
174
|
-
if (this.events.length && !this.
|
|
175
|
-
this.
|
|
174
|
+
if (this.events.length && !this.saving) {
|
|
175
|
+
this.saveEvent();
|
|
176
176
|
}
|
|
177
177
|
if (this.pending.length && !this.executing) {
|
|
178
178
|
this.dispatch();
|
|
@@ -190,33 +190,36 @@ class Dispatcher {
|
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
192
|
if (this.events.length) {
|
|
193
|
-
this.
|
|
193
|
+
this.saveEvent();
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
this.executing = (async () => {
|
|
197
197
|
let next = null;
|
|
198
|
-
|
|
199
|
-
if (this.
|
|
200
|
-
const
|
|
201
|
-
|
|
198
|
+
const pending = this.pending.shift() ?? null;
|
|
199
|
+
if (pending || this.ready && this.plugin.serving()) {
|
|
200
|
+
const execution = await this.prepare((pending == null ? void 0 : pending.execution) ?? null, {
|
|
201
|
+
immediate: pending == null ? void 0 : pending.immediate
|
|
202
|
+
});
|
|
202
203
|
if (execution) {
|
|
203
|
-
next = [execution, pending.job];
|
|
204
|
+
next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
|
|
205
|
+
}
|
|
206
|
+
if (pending && next) {
|
|
204
207
|
this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
|
|
205
208
|
}
|
|
206
209
|
} else {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
next = [execution];
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
this.plugin.getLogger("dispatcher").warn(
|
|
214
|
-
`${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
|
|
215
|
-
);
|
|
216
|
-
}
|
|
210
|
+
this.plugin.getLogger("dispatcher").warn(
|
|
211
|
+
`${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
|
|
212
|
+
);
|
|
217
213
|
}
|
|
218
214
|
if (next) {
|
|
219
|
-
|
|
215
|
+
try {
|
|
216
|
+
await this.process(next[0], next[1], { rerun: next[2] });
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
|
|
219
|
+
if (pending && (0, import_utils.isLockAcquireError)(error)) {
|
|
220
|
+
this.pending.unshift({ ...pending, execution: next[0], immediate: true });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
220
223
|
}
|
|
221
224
|
setImmediate(() => {
|
|
222
225
|
this.executing = null;
|
|
@@ -228,7 +231,10 @@ class Dispatcher {
|
|
|
228
231
|
})();
|
|
229
232
|
}
|
|
230
233
|
async run(pending) {
|
|
231
|
-
this.pending.push(
|
|
234
|
+
this.pending.push({
|
|
235
|
+
...pending,
|
|
236
|
+
immediate: !this.executing && !this.pending.length && !this.saving && !this.events.length
|
|
237
|
+
});
|
|
232
238
|
this.dispatch();
|
|
233
239
|
}
|
|
234
240
|
async triggerSync(workflow, context, { deferred, ...options } = {}) {
|
|
@@ -236,13 +242,23 @@ class Dispatcher {
|
|
|
236
242
|
try {
|
|
237
243
|
execution = await this.createExecution(workflow, context, options);
|
|
238
244
|
} catch (err) {
|
|
239
|
-
|
|
245
|
+
if (err instanceof Error) {
|
|
246
|
+
this.plugin.getLogger(workflow.id).error(`creating execution failed: ${err.message}`, err);
|
|
247
|
+
}
|
|
240
248
|
return null;
|
|
241
249
|
}
|
|
242
250
|
try {
|
|
243
|
-
|
|
251
|
+
const entered = await this.prepare(execution, {
|
|
252
|
+
transaction: this.plugin.useDataSourceTransaction("main", options.transaction)
|
|
253
|
+
});
|
|
254
|
+
if (!entered) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return this.process(entered, void 0, options);
|
|
244
258
|
} catch (err) {
|
|
245
|
-
|
|
259
|
+
if (err instanceof Error) {
|
|
260
|
+
this.plugin.getLogger(execution.workflowId).error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
261
|
+
}
|
|
246
262
|
}
|
|
247
263
|
return null;
|
|
248
264
|
}
|
|
@@ -252,9 +268,9 @@ class Dispatcher {
|
|
|
252
268
|
if (!triggerValid) {
|
|
253
269
|
return false;
|
|
254
270
|
}
|
|
255
|
-
const { stack } = options;
|
|
271
|
+
const { stack = [] } = options;
|
|
256
272
|
let valid = true;
|
|
257
|
-
if (
|
|
273
|
+
if (stack == null ? void 0 : stack.length) {
|
|
258
274
|
const existed = await workflow.countExecutions({
|
|
259
275
|
where: {
|
|
260
276
|
id: stack
|
|
@@ -264,7 +280,7 @@ class Dispatcher {
|
|
|
264
280
|
const limitCount = workflow.options.stackLimit || 1;
|
|
265
281
|
if (existed >= limitCount) {
|
|
266
282
|
this.plugin.getLogger(workflow.id).warn(
|
|
267
|
-
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call
|
|
283
|
+
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call count is ${limitCount}, newly triggering will be skipped.`
|
|
268
284
|
);
|
|
269
285
|
valid = false;
|
|
270
286
|
}
|
|
@@ -276,13 +292,21 @@ class Dispatcher {
|
|
|
276
292
|
const { deferred } = options;
|
|
277
293
|
const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
|
|
278
294
|
const sameTransaction = options.transaction === transaction;
|
|
279
|
-
|
|
295
|
+
let stack = options.stack;
|
|
296
|
+
if (options.parentExecutionId && !stack) {
|
|
297
|
+
const parentExecution = await this.plugin.db.getRepository("executions").findOne({
|
|
298
|
+
filterByTk: options.parentExecutionId,
|
|
299
|
+
transaction
|
|
300
|
+
});
|
|
301
|
+
stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
|
|
302
|
+
}
|
|
303
|
+
const valid = await this.validateEvent(workflow, context, { ...options, stack, transaction });
|
|
280
304
|
if (!valid) {
|
|
281
305
|
if (!sameTransaction) {
|
|
282
306
|
await transaction.commit();
|
|
283
307
|
}
|
|
284
308
|
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
285
|
-
|
|
309
|
+
throw new Error("event is not valid");
|
|
286
310
|
}
|
|
287
311
|
let execution;
|
|
288
312
|
try {
|
|
@@ -291,7 +315,8 @@ class Dispatcher {
|
|
|
291
315
|
context,
|
|
292
316
|
key: workflow.key,
|
|
293
317
|
eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
|
|
294
|
-
stack
|
|
318
|
+
stack,
|
|
319
|
+
parentExecutionId: options.parentExecutionId ?? null,
|
|
295
320
|
dispatched: deferred ?? false,
|
|
296
321
|
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
|
|
297
322
|
manually: options.manually
|
|
@@ -325,92 +350,135 @@ class Dispatcher {
|
|
|
325
350
|
execution.workflow = workflow;
|
|
326
351
|
return execution;
|
|
327
352
|
}
|
|
328
|
-
async
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
353
|
+
async prepare(input, options = {}) {
|
|
354
|
+
const transaction = options.transaction;
|
|
355
|
+
const ownTransaction = !transaction;
|
|
356
|
+
const tx = transaction || await this.plugin.db.sequelize.transaction({
|
|
357
|
+
isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
|
|
358
|
+
});
|
|
359
|
+
const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
|
|
332
360
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
{
|
|
337
|
-
{
|
|
338
|
-
where: {
|
|
339
|
-
id: execution.id,
|
|
340
|
-
dispatched: false
|
|
341
|
-
},
|
|
342
|
-
transaction
|
|
343
|
-
}
|
|
344
|
-
);
|
|
345
|
-
if (!affected) {
|
|
346
|
-
fetched = null;
|
|
347
|
-
return;
|
|
361
|
+
let execution = input;
|
|
362
|
+
if (execution) {
|
|
363
|
+
if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
364
|
+
await execution.reload({ transaction: tx });
|
|
348
365
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
async (transaction) => {
|
|
365
|
-
const execution = await this.plugin.db.getRepository("executions").findOne({
|
|
366
|
-
filter: {
|
|
367
|
-
dispatched: false,
|
|
368
|
-
"workflow.enabled": true
|
|
369
|
-
},
|
|
370
|
-
sort: "id",
|
|
371
|
-
transaction
|
|
372
|
-
});
|
|
373
|
-
if (execution) {
|
|
374
|
-
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
375
|
-
await execution.update(
|
|
376
|
-
{
|
|
377
|
-
dispatched: true,
|
|
378
|
-
status: import_constants.EXECUTION_STATUS.STARTED
|
|
379
|
-
},
|
|
380
|
-
{ transaction }
|
|
381
|
-
);
|
|
382
|
-
execution.workflow = this.plugin.enabledCache.get(execution.workflowId);
|
|
383
|
-
fetched = execution;
|
|
384
|
-
} else {
|
|
385
|
-
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
386
|
-
}
|
|
366
|
+
} else {
|
|
367
|
+
execution = await this.plugin.db.getRepository("executions").findOne({
|
|
368
|
+
filter: {
|
|
369
|
+
dispatched: false,
|
|
370
|
+
"workflow.enabled": true
|
|
371
|
+
},
|
|
372
|
+
sort: "id",
|
|
373
|
+
transaction: tx,
|
|
374
|
+
lock: tx.LOCK.UPDATE,
|
|
375
|
+
skipLocked: true
|
|
376
|
+
});
|
|
377
|
+
if (execution) {
|
|
378
|
+
this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
379
|
+
} else {
|
|
380
|
+
this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
|
|
387
381
|
}
|
|
388
|
-
|
|
382
|
+
}
|
|
383
|
+
if (!execution) {
|
|
384
|
+
if (ownTransaction) {
|
|
385
|
+
await tx.commit();
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const entered = await this.enter(execution, tx);
|
|
390
|
+
if (ownTransaction) {
|
|
391
|
+
await tx.commit();
|
|
392
|
+
}
|
|
393
|
+
return entered;
|
|
389
394
|
} catch (error) {
|
|
390
|
-
|
|
395
|
+
if (ownTransaction) {
|
|
396
|
+
await tx.rollback();
|
|
397
|
+
}
|
|
398
|
+
if (error instanceof Error) {
|
|
399
|
+
logger.error(`entering execution failed: ${error.message}`, { error });
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
391
402
|
}
|
|
392
|
-
return fetched;
|
|
393
403
|
}
|
|
394
|
-
async
|
|
395
|
-
|
|
396
|
-
|
|
404
|
+
async enter(execution, transaction) {
|
|
405
|
+
const workflow = execution.workflow || this.plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
|
|
406
|
+
if (!workflow) {
|
|
407
|
+
this.plugin.getLogger(execution.workflowId).warn(`workflow (${execution.workflowId}) not found for execution`, {
|
|
408
|
+
workflowId: execution.workflowId,
|
|
409
|
+
executionId: execution.id
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
if (execution.status && execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
if (execution.dispatched && execution.status === import_constants.EXECUTION_STATUS.STARTED && execution.startedAt) {
|
|
416
|
+
execution.workflow = workflow;
|
|
417
|
+
return execution;
|
|
418
|
+
}
|
|
419
|
+
const values = {
|
|
420
|
+
dispatched: true,
|
|
421
|
+
status: import_constants.EXECUTION_STATUS.STARTED
|
|
422
|
+
};
|
|
423
|
+
const where = {
|
|
424
|
+
id: execution.id,
|
|
425
|
+
status: execution.status ?? null
|
|
426
|
+
};
|
|
397
427
|
if (!execution.dispatched) {
|
|
398
|
-
|
|
399
|
-
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
400
|
-
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
428
|
+
where.dispatched = false;
|
|
401
429
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
430
|
+
if (!execution.startedAt) {
|
|
431
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
432
|
+
values.startedAt = startedAt;
|
|
433
|
+
execution.workflow = workflow;
|
|
434
|
+
values.expiresAt = this.plugin.timeoutManager.getExpiresAt(execution, startedAt);
|
|
435
|
+
where.startedAt = null;
|
|
436
|
+
}
|
|
437
|
+
const ExecutionModelClass = this.plugin.db.getModel("executions");
|
|
438
|
+
const [affected] = await ExecutionModelClass.update(values, {
|
|
439
|
+
where,
|
|
440
|
+
transaction
|
|
441
|
+
});
|
|
442
|
+
if (!affected) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
await execution.reload({ transaction });
|
|
446
|
+
execution.workflow = workflow;
|
|
447
|
+
return execution;
|
|
448
|
+
}
|
|
449
|
+
async process(execution, job = null, options = {}) {
|
|
450
|
+
const { rerun, ...processorOptions } = options;
|
|
451
|
+
const logger = this.plugin.getLogger(execution.workflowId);
|
|
452
|
+
const run = async () => {
|
|
453
|
+
var _a, _b, _c;
|
|
454
|
+
if (!execution.dispatched) {
|
|
455
|
+
const transaction = await this.plugin.useDataSourceTransaction("main", processorOptions.transaction);
|
|
456
|
+
await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
457
|
+
logger.info(`execution (${execution.id}) from pending list updated to started`);
|
|
410
458
|
}
|
|
411
|
-
|
|
412
|
-
|
|
459
|
+
this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
|
|
460
|
+
const processor = this.plugin.createProcessor(execution, processorOptions);
|
|
461
|
+
logger.info(`execution (${execution.id}) ${rerun ? "rerunning" : job ? "resuming" : "starting"}...`);
|
|
462
|
+
try {
|
|
463
|
+
await (rerun ? processor.rerun(rerun) : job ? processor.resume(job) : processor.start());
|
|
464
|
+
logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
|
|
465
|
+
logger.debug(`execution (${execution.id}) details:`, { execution });
|
|
466
|
+
if (execution.status && ((_c = (_b = (_a = execution.workflow) == null ? void 0 : _a.options) == null ? void 0 : _b.deleteExecutionOnStatus) == null ? void 0 : _c.includes(execution.status))) {
|
|
467
|
+
await execution.destroy({ transaction: processor.mainTransaction });
|
|
468
|
+
}
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (err instanceof Error) {
|
|
471
|
+
logger.error(`execution (${execution.id}) error: ${err.message}`, err);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return processor;
|
|
475
|
+
};
|
|
476
|
+
const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(execution.id), 6e4);
|
|
477
|
+
try {
|
|
478
|
+
return await lock.runExclusive(run, 6e4);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
logger.error(`execution (${execution.id}) could not acquire process lock`, { error });
|
|
481
|
+
throw error;
|
|
413
482
|
}
|
|
414
|
-
return processor;
|
|
415
483
|
}
|
|
416
484
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Transactionable } from '@nocobase/database';
|
|
10
|
+
import type PluginWorkflowServer from './Plugin';
|
|
11
|
+
import type { ExecutionModel } from './types';
|
|
12
|
+
export default class ExecutionTimeoutManager {
|
|
13
|
+
private readonly plugin;
|
|
14
|
+
private readonly timers;
|
|
15
|
+
private scanTimer;
|
|
16
|
+
private nextExpiresAtTimer;
|
|
17
|
+
private nextExpiresAt;
|
|
18
|
+
private scanning;
|
|
19
|
+
private stopped;
|
|
20
|
+
constructor(plugin: PluginWorkflowServer);
|
|
21
|
+
getTimeout(execution: ExecutionModel): number;
|
|
22
|
+
getExpiresAt(execution: ExecutionModel, startedAt: Date): Date;
|
|
23
|
+
load(): Promise<void>;
|
|
24
|
+
unload(): Promise<void>;
|
|
25
|
+
isExpired(execution: ExecutionModel, now?: Date): boolean;
|
|
26
|
+
getRemainingMs(execution: ExecutionModel, now?: Date): number;
|
|
27
|
+
abort(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
28
|
+
abortExecutionIfExpired(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
29
|
+
clear(executionId: number | string): void;
|
|
30
|
+
shouldContinue(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Owner-only per-execution timer. Only call from code paths that have acquired
|
|
33
|
+
* local execution ownership, such as Dispatcher/Processor processing paths.
|
|
34
|
+
*/
|
|
35
|
+
scheduleExecutionTimeout(execution: ExecutionModel): void;
|
|
36
|
+
invalidateNextExpiresAtIfMatches(expiresAt?: Date | null): void;
|
|
37
|
+
private scanExpiredExecutions;
|
|
38
|
+
private scheduleScan;
|
|
39
|
+
private scheduleNextExpiresAtTimer;
|
|
40
|
+
private scheduleNextExpiresAtIfEarlier;
|
|
41
|
+
private handleNextExpiresAtTimeout;
|
|
42
|
+
private clearNextExpiresAtTimer;
|
|
43
|
+
private clearExecutionTimeout;
|
|
44
|
+
private handleExecutionTimeout;
|
|
45
|
+
}
|