@nocobase/plugin-async-task-manager 2.0.0-alpha.9 → 2.0.0-beta.10
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/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/common/collections/asyncTasks.d.ts +2 -0
- package/dist/common/collections/asyncTasks.js +2 -0
- package/dist/common/constants.d.ts +2 -2
- package/dist/common/constants.js +1 -1
- package/dist/externalVersion.js +7 -6
- package/dist/locale/de-DE.json +31 -31
- package/dist/locale/en-US.json +42 -1
- package/dist/locale/es-ES.json +45 -0
- package/dist/locale/fr-FR.json +45 -0
- package/dist/locale/hu-HU.json +45 -0
- package/dist/locale/id-ID.json +45 -0
- package/dist/locale/it-IT.json +31 -31
- package/dist/locale/ja-JP.json +45 -0
- package/dist/locale/ko-KR.json +45 -0
- package/dist/locale/nl-NL.json +44 -45
- package/dist/locale/pt-BR.json +45 -0
- package/dist/locale/ru-RU.json +45 -0
- package/dist/locale/tr-TR.json +45 -0
- package/dist/locale/uk-UA.json +45 -0
- package/dist/locale/vi-VN.json +45 -0
- package/dist/locale/zh-CN.json +33 -31
- package/dist/locale/zh-TW.json +45 -0
- package/dist/server/base-concurrency-monitor.d.ts +20 -0
- package/dist/server/base-concurrency-monitor.js +63 -0
- package/dist/server/base-task-manager.d.ts +16 -1
- package/dist/server/base-task-manager.js +88 -30
- package/dist/server/command-task-type.d.ts +1 -0
- package/dist/server/command-task-type.js +54 -4
- package/dist/server/interfaces/concurrency-monitor.d.ts +16 -0
- package/dist/server/interfaces/concurrency-monitor.js +24 -0
- package/dist/server/task-type.js +3 -2
- package/package.json +4 -2
|
@@ -36,14 +36,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var base_task_manager_exports = {};
|
|
38
38
|
__export(base_task_manager_exports, {
|
|
39
|
-
BaseTaskManager: () => BaseTaskManager
|
|
39
|
+
BaseTaskManager: () => BaseTaskManager,
|
|
40
|
+
ConcurrencyMonitorDelegate: () => ConcurrencyMonitorDelegate
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(base_task_manager_exports);
|
|
42
43
|
var import_lodash = require("lodash");
|
|
43
44
|
var import_constants = require("../common/constants");
|
|
44
45
|
var import_crypto = require("crypto");
|
|
45
46
|
var import_plugin = __toESM(require("./plugin"));
|
|
47
|
+
var import_base_concurrency_monitor = require("./base-concurrency-monitor");
|
|
46
48
|
const WORKER_JOB_ASYNC_TASK_PROCESS = "async-task:process";
|
|
49
|
+
const CONCURRENCY = process.env.ASYNC_TASK_MAX_CONCURRENCY ? Number.parseInt(process.env.ASYNC_TASK_MAX_CONCURRENCY, 10) : 3;
|
|
50
|
+
const CONCURRENCY_MODE = process.env.ASYNC_TASK_CONCURRENCY_MODE ?? "app";
|
|
51
|
+
const PROCESS_CONCURRENCY_MONITOR = new import_base_concurrency_monitor.BaseConcurrencyMonitor(CONCURRENCY);
|
|
47
52
|
class BaseTaskManager {
|
|
48
53
|
taskTypes = /* @__PURE__ */ new Map();
|
|
49
54
|
tasks = /* @__PURE__ */ new Map();
|
|
@@ -53,13 +58,25 @@ class BaseTaskManager {
|
|
|
53
58
|
logger;
|
|
54
59
|
app;
|
|
55
60
|
progressThrottles = /* @__PURE__ */ new Map();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return this.
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
+
concurrencyMonitor = new ConcurrencyMonitorDelegate();
|
|
62
|
+
get concurrency() {
|
|
63
|
+
return this.concurrencyMonitor.concurrency;
|
|
64
|
+
}
|
|
65
|
+
set concurrency(concurrency) {
|
|
66
|
+
this.concurrencyMonitor.concurrency = concurrency;
|
|
67
|
+
}
|
|
68
|
+
idle = () => this.app.serving(WORKER_JOB_ASYNC_TASK_PROCESS) && this.concurrencyMonitor.idle();
|
|
69
|
+
onQueueTask = async ({ id }, { queueOptions }) => {
|
|
61
70
|
const task = await this.prepareTask(id);
|
|
62
|
-
|
|
71
|
+
if (!this.concurrencyMonitor.increase(task.record.id)) {
|
|
72
|
+
this.enqueueTask(task, queueOptions);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await this.runTask(task);
|
|
77
|
+
} finally {
|
|
78
|
+
this.concurrencyMonitor.decrease(task.record.id);
|
|
79
|
+
}
|
|
63
80
|
};
|
|
64
81
|
onTaskProgress = (item) => {
|
|
65
82
|
const userId = item.createdById;
|
|
@@ -105,6 +122,7 @@ class BaseTaskManager {
|
|
|
105
122
|
if (task.doneAt) {
|
|
106
123
|
this.progressThrottles.delete(task.id);
|
|
107
124
|
this.tasks.delete(task.id);
|
|
125
|
+
this.concurrencyMonitor.decrease(task.id);
|
|
108
126
|
}
|
|
109
127
|
if (task.status === import_constants.TASK_STATUS.SUCCEEDED) {
|
|
110
128
|
this.app.emit("workflow:dispatch");
|
|
@@ -113,6 +131,7 @@ class BaseTaskManager {
|
|
|
113
131
|
onTaskAfterDelete = (task) => {
|
|
114
132
|
this.tasks.delete(task.id);
|
|
115
133
|
this.progressThrottles.delete(task.id);
|
|
134
|
+
this.concurrencyMonitor.decrease(task.id);
|
|
116
135
|
const userId = task.createdById;
|
|
117
136
|
if (userId) {
|
|
118
137
|
this.app.emit("ws:sendToUser", {
|
|
@@ -130,31 +149,44 @@ class BaseTaskManager {
|
|
|
130
149
|
cleanup = async () => {
|
|
131
150
|
this.logger.debug("Running cleanup for completed tasks...");
|
|
132
151
|
const TaskRepo = this.app.db.getRepository("asyncTasks");
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
try {
|
|
153
|
+
const tasksToCleanup = await TaskRepo.find({
|
|
154
|
+
fields: ["id"],
|
|
155
|
+
filter: {
|
|
156
|
+
$or: [
|
|
157
|
+
{
|
|
158
|
+
status: [import_constants.TASK_STATUS.SUCCEEDED, import_constants.TASK_STATUS.FAILED],
|
|
159
|
+
doneAt: {
|
|
160
|
+
$lt: new Date(Date.now() - this.cleanupDelay)
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
status: import_constants.TASK_STATUS.CANCELED
|
|
141
165
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
this.logger.debug(`Found ${tasksToCleanup.length} tasks to cleanup`);
|
|
170
|
+
if (tasksToCleanup.length) {
|
|
171
|
+
for (const task of tasksToCleanup) {
|
|
172
|
+
this.tasks.delete(task.id);
|
|
173
|
+
this.progressThrottles.delete(task.id);
|
|
174
|
+
this.concurrencyMonitor.decrease(task.id);
|
|
175
|
+
}
|
|
176
|
+
await TaskRepo.destroy({
|
|
177
|
+
filterByTk: tasksToCleanup.map((task) => task.id),
|
|
178
|
+
individualHooks: true
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.logger.error(`DB error during cleanup: ${error.message}. Will stop cleanup timer.`, { error });
|
|
183
|
+
if (error.name === "SequelizeConnectionAcquireTimeoutError") {
|
|
184
|
+
if (this.cleanupTimer) {
|
|
185
|
+
clearInterval(this.cleanupTimer);
|
|
186
|
+
this.cleanupTimer = null;
|
|
187
|
+
}
|
|
147
188
|
}
|
|
148
|
-
});
|
|
149
|
-
this.logger.debug(`Found ${tasksToCleanup.length} tasks to cleanup`);
|
|
150
|
-
for (const task of tasksToCleanup) {
|
|
151
|
-
this.tasks.delete(task.id);
|
|
152
|
-
this.progressThrottles.delete(task.id);
|
|
153
189
|
}
|
|
154
|
-
await TaskRepo.destroy({
|
|
155
|
-
filterByTk: tasksToCleanup.map((task) => task.id),
|
|
156
|
-
individualHooks: true
|
|
157
|
-
});
|
|
158
190
|
};
|
|
159
191
|
getThrottledProgressEmitter(taskId, userId) {
|
|
160
192
|
if (!this.progressThrottles.has(taskId)) {
|
|
@@ -314,7 +346,33 @@ class BaseTaskManager {
|
|
|
314
346
|
}
|
|
315
347
|
}
|
|
316
348
|
}
|
|
349
|
+
class ConcurrencyMonitorDelegate {
|
|
350
|
+
constructor(mode = CONCURRENCY_MODE, appConcurrencyMonitor = new import_base_concurrency_monitor.BaseConcurrencyMonitor(CONCURRENCY), processConcurrencyMonitor = PROCESS_CONCURRENCY_MONITOR) {
|
|
351
|
+
this.mode = mode;
|
|
352
|
+
this.appConcurrencyMonitor = appConcurrencyMonitor;
|
|
353
|
+
this.processConcurrencyMonitor = processConcurrencyMonitor;
|
|
354
|
+
}
|
|
355
|
+
get concurrencyMonitor() {
|
|
356
|
+
return this.mode === "process" ? this.processConcurrencyMonitor : this.appConcurrencyMonitor;
|
|
357
|
+
}
|
|
358
|
+
idle() {
|
|
359
|
+
return this.concurrencyMonitor.idle();
|
|
360
|
+
}
|
|
361
|
+
get concurrency() {
|
|
362
|
+
return this.concurrencyMonitor.concurrency;
|
|
363
|
+
}
|
|
364
|
+
set concurrency(concurrency) {
|
|
365
|
+
this.concurrencyMonitor.concurrency = concurrency;
|
|
366
|
+
}
|
|
367
|
+
increase(taskId) {
|
|
368
|
+
return this.concurrencyMonitor.increase(taskId);
|
|
369
|
+
}
|
|
370
|
+
decrease(taskId) {
|
|
371
|
+
this.concurrencyMonitor.decrease(taskId);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
317
374
|
// Annotate the CommonJS export names for ESM import in node:
|
|
318
375
|
0 && (module.exports = {
|
|
319
|
-
BaseTaskManager
|
|
376
|
+
BaseTaskManager,
|
|
377
|
+
ConcurrencyMonitorDelegate
|
|
320
378
|
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
/// <reference types="node" />
|
|
10
10
|
import { Worker } from 'worker_threads';
|
|
11
11
|
import { TaskType } from './task-type';
|
|
12
|
+
export declare function parseArgv(list: string[]): any;
|
|
12
13
|
export declare class CommandTaskType extends TaskType {
|
|
13
14
|
static type: string;
|
|
14
15
|
workerThread: Worker;
|
|
@@ -36,7 +36,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var command_task_type_exports = {};
|
|
38
38
|
__export(command_task_type_exports, {
|
|
39
|
-
CommandTaskType: () => CommandTaskType
|
|
39
|
+
CommandTaskType: () => CommandTaskType,
|
|
40
|
+
parseArgv: () => parseArgv
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(command_task_type_exports);
|
|
42
43
|
var import_async_task_manager = require("./interfaces/async-task-manager");
|
|
@@ -44,6 +45,51 @@ var import_node_process = __toESM(require("node:process"));
|
|
|
44
45
|
var import_worker_threads = require("worker_threads");
|
|
45
46
|
var import_path = __toESM(require("path"));
|
|
46
47
|
var import_task_type = require("./task-type");
|
|
48
|
+
const getResourceLimitsFromEnv = () => {
|
|
49
|
+
let resourceLimitsUndefined = true;
|
|
50
|
+
const resourceLimits = {};
|
|
51
|
+
if (import_node_process.default.env.ASYNC_TASK_WORKER_MAX_OLD) {
|
|
52
|
+
resourceLimits.maxOldGenerationSizeMb = Number.parseInt(import_node_process.default.env.ASYNC_TASK_WORKER_MAX_OLD, 10);
|
|
53
|
+
resourceLimitsUndefined = false;
|
|
54
|
+
}
|
|
55
|
+
if (import_node_process.default.env.ASYNC_TASK_WORKER_MAX_YOUNG) {
|
|
56
|
+
resourceLimits.maxYoungGenerationSizeMb = Number.parseInt(import_node_process.default.env.ASYNC_TASK_WORKER_MAX_YOUNG, 10);
|
|
57
|
+
resourceLimitsUndefined = false;
|
|
58
|
+
}
|
|
59
|
+
return resourceLimitsUndefined ? void 0 : resourceLimits;
|
|
60
|
+
};
|
|
61
|
+
const RESOURCE_LIMITS = getResourceLimitsFromEnv();
|
|
62
|
+
function parseArgv(list) {
|
|
63
|
+
const argv = {};
|
|
64
|
+
for (const item of list) {
|
|
65
|
+
const match = item.match(/^--([^=]+)=(.*)$/);
|
|
66
|
+
if (match) {
|
|
67
|
+
const key = match[1];
|
|
68
|
+
let value = match[2];
|
|
69
|
+
if (value.startsWith("{") || value.startsWith("[")) {
|
|
70
|
+
try {
|
|
71
|
+
value = JSON.parse(value);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (value === "true") {
|
|
76
|
+
value = true;
|
|
77
|
+
} else if (value === "false") {
|
|
78
|
+
value = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
argv[key] = value;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const parts = item.split(":");
|
|
85
|
+
if (parts.length === 2) {
|
|
86
|
+
const command = parts[0];
|
|
87
|
+
const commandValue = parts[1];
|
|
88
|
+
argv[command] = commandValue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return argv;
|
|
92
|
+
}
|
|
47
93
|
class CommandTaskType extends import_task_type.TaskType {
|
|
48
94
|
static type = "command";
|
|
49
95
|
workerThread;
|
|
@@ -55,6 +101,7 @@ class CommandTaskType extends import_task_type.TaskType {
|
|
|
55
101
|
async execute() {
|
|
56
102
|
var _a;
|
|
57
103
|
const { argv } = this.record.params;
|
|
104
|
+
const parsedArgv = parseArgv(argv);
|
|
58
105
|
const isDev = (((_a = import_node_process.default.argv[1]) == null ? void 0 : _a.endsWith(".ts")) || import_node_process.default.argv[1].includes("tinypool")) ?? false;
|
|
59
106
|
const appRoot = import_node_process.default.env.APP_PACKAGE_ROOT || "packages/core/app";
|
|
60
107
|
const workerPath = import_path.default.resolve(import_node_process.default.cwd(), appRoot, isDev ? "src/index.ts" : "lib/index.js");
|
|
@@ -73,8 +120,10 @@ class CommandTaskType extends import_task_type.TaskType {
|
|
|
73
120
|
},
|
|
74
121
|
env: {
|
|
75
122
|
...import_node_process.default.env,
|
|
76
|
-
WORKER_MODE: "-"
|
|
77
|
-
|
|
123
|
+
WORKER_MODE: "-",
|
|
124
|
+
...parsedArgv.app && parsedArgv.app !== "main" ? { STARTUP_SUBAPP: parsedArgv.app } : {}
|
|
125
|
+
},
|
|
126
|
+
resourceLimits: RESOURCE_LIMITS
|
|
78
127
|
});
|
|
79
128
|
this.workerThread = worker;
|
|
80
129
|
(_b = this.logger) == null ? void 0 : _b.debug(`Worker created successfully for task ${this.record.id}`);
|
|
@@ -128,5 +177,6 @@ class CommandTaskType extends import_task_type.TaskType {
|
|
|
128
177
|
}
|
|
129
178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
130
179
|
0 && (module.exports = {
|
|
131
|
-
CommandTaskType
|
|
180
|
+
CommandTaskType,
|
|
181
|
+
parseArgv
|
|
132
182
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
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 { TaskId } from '../../common/types';
|
|
10
|
+
export type ConcurrencyMode = 'app' | 'process';
|
|
11
|
+
export interface ConcurrencyMonitor {
|
|
12
|
+
idle(): boolean;
|
|
13
|
+
concurrency: number;
|
|
14
|
+
increase(taskId: TaskId): boolean;
|
|
15
|
+
decrease(taskId: TaskId): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
var concurrency_monitor_exports = {};
|
|
24
|
+
module.exports = __toCommonJS(concurrency_monitor_exports);
|
package/dist/server/task-type.js
CHANGED
|
@@ -116,11 +116,12 @@ class TaskType {
|
|
|
116
116
|
try {
|
|
117
117
|
const result = await this.execute();
|
|
118
118
|
(_c = this.logger) == null ? void 0 : _c.info(`Task ${this.record.id} completed successfully with result: ${JSON.stringify(result)}`);
|
|
119
|
-
|
|
119
|
+
this.record.set({
|
|
120
120
|
status: import_constants.TASK_STATUS.SUCCEEDED,
|
|
121
121
|
doneAt: /* @__PURE__ */ new Date(),
|
|
122
122
|
result
|
|
123
123
|
});
|
|
124
|
+
await this.record.save();
|
|
124
125
|
} catch (error) {
|
|
125
126
|
if (error instanceof import_async_task_manager.CancelError) {
|
|
126
127
|
(_d = this.logger) == null ? void 0 : _d.info(`Task ${this.record.id} was cancelled during execution`);
|
|
@@ -129,7 +130,7 @@ class TaskType {
|
|
|
129
130
|
await this.record.update({
|
|
130
131
|
status: import_constants.TASK_STATUS.FAILED,
|
|
131
132
|
doneAt: /* @__PURE__ */ new Date(),
|
|
132
|
-
error: error.
|
|
133
|
+
result: { ...error, message: error.message }
|
|
133
134
|
});
|
|
134
135
|
(_e = this.logger) == null ? void 0 : _e.error(`Task ${this.record.id} failed with error: ${error.message}`);
|
|
135
136
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/plugin-async-task-manager",
|
|
3
3
|
"displayName": "Async task manager",
|
|
4
|
+
"displayName.ru-RU": "Менеджер асинхронных задач",
|
|
4
5
|
"displayName.zh-CN": "异步任务管理器",
|
|
5
6
|
"description": "Manage and monitor asynchronous tasks such as data import/export. Support task progress tracking and notification.",
|
|
7
|
+
"description.ru-RU": "Управление асинхронными задачами и мониторинг (например, импорт/экспорт данных). Поддержка отслеживания прогресса и уведомлений о задачах.",
|
|
6
8
|
"description.zh-CN": "管理和监控数据导入导出等异步任务。支持任务进度跟踪和通知。",
|
|
7
|
-
"version": "2.0.0-
|
|
9
|
+
"version": "2.0.0-beta.10",
|
|
8
10
|
"main": "dist/server/index.js",
|
|
9
11
|
"peerDependencies": {
|
|
10
12
|
"@nocobase/client": "2.x",
|
|
@@ -15,5 +17,5 @@
|
|
|
15
17
|
"dependencies": {
|
|
16
18
|
"p-queue": "^6.6.2"
|
|
17
19
|
},
|
|
18
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "9943dc4b0fdedcac3f304714b58635ae441e2560"
|
|
19
21
|
}
|