@tachybase/module-worker-thread 0.23.8
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/.turbo/turbo-build.log +14 -0
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/WorkerInfoPane.d.ts +2 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +1 -0
- package/dist/client/locale.d.ts +4 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +27 -0
- package/dist/externalVersion.js +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/locale/en-US.json +10 -0
- package/dist/locale/zh-CN.json +10 -0
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.js +42 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +33 -0
- package/dist/server/plugin.d.ts +11 -0
- package/dist/server/plugin.js +133 -0
- package/dist/server/worker.d.ts +1 -0
- package/dist/server/worker.js +157 -0
- package/dist/server/workerManager.d.ts +49 -0
- package/dist/server/workerManager.js +343 -0
- package/dist/server/workerTypes.d.ts +25 -0
- package/dist/server/workerTypes.js +31 -0
- package/dist/server/workerWebController.d.ts +5 -0
- package/dist/server/workerWebController.js +124 -0
- package/dist/types/index.d.js +0 -0
- package/package.json +26 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/worker-starter.mjs +8 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var worker_exports = {};
|
|
29
|
+
__export(worker_exports, {
|
|
30
|
+
main: () => main
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(worker_exports);
|
|
33
|
+
var import_worker_threads = require("worker_threads");
|
|
34
|
+
var import_logger = require("@tachybase/logger");
|
|
35
|
+
var import_module_collection = __toESM(require("@tachybase/module-collection"));
|
|
36
|
+
var import_module_user = __toESM(require("@tachybase/module-user"));
|
|
37
|
+
var import_server = require("@tachybase/server");
|
|
38
|
+
var import_utils = require("@tachybase/utils");
|
|
39
|
+
var import_workerTypes = require("./workerTypes");
|
|
40
|
+
const loggerOptions = {
|
|
41
|
+
system: {
|
|
42
|
+
transports: (0, import_logger.getLoggerTransport)(),
|
|
43
|
+
level: (0, import_logger.getLoggerLevel)()
|
|
44
|
+
// TODO: 后续考虑是否提到info
|
|
45
|
+
},
|
|
46
|
+
request: {
|
|
47
|
+
transports: (0, import_logger.getLoggerTransport)(),
|
|
48
|
+
level: (0, import_logger.getLoggerLevel)()
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const handleWorkerMessages = (app) => {
|
|
52
|
+
if (import_worker_threads.isMainThread) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
import_worker_threads.parentPort.postMessage({
|
|
56
|
+
event: import_workerTypes.WorkerEvent.Started
|
|
57
|
+
});
|
|
58
|
+
import_worker_threads.parentPort.on("message", async (message) => {
|
|
59
|
+
app.logger.info("[worker] input", message);
|
|
60
|
+
const { reqId, event } = message;
|
|
61
|
+
if (event === import_workerTypes.WorkerEvent.PluginMethod) {
|
|
62
|
+
try {
|
|
63
|
+
const { plugin, method, params } = message.values;
|
|
64
|
+
const appPlugin = app.pm.get(plugin);
|
|
65
|
+
const result = await appPlugin[method](params);
|
|
66
|
+
app.logger.info(`[worker] output ${result}`);
|
|
67
|
+
import_worker_threads.parentPort.postMessage({
|
|
68
|
+
event,
|
|
69
|
+
reqId,
|
|
70
|
+
result
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
app.logger.error("[worker] error", error);
|
|
74
|
+
import_worker_threads.parentPort.postMessage({
|
|
75
|
+
event,
|
|
76
|
+
reqId,
|
|
77
|
+
error
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
app.logger.error("[worker] invalid message", message);
|
|
82
|
+
import_worker_threads.parentPort.postMessage({
|
|
83
|
+
event,
|
|
84
|
+
reqId,
|
|
85
|
+
error: "invalid message"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const main = async () => {
|
|
91
|
+
var _a, _b, _c;
|
|
92
|
+
let app;
|
|
93
|
+
const appName = import_worker_threads.workerData.appName || "main";
|
|
94
|
+
try {
|
|
95
|
+
const applicationOptions = {
|
|
96
|
+
name: `worker-${appName}-${(0, import_utils.uid)()}`,
|
|
97
|
+
database: import_worker_threads.workerData.databaseOptions,
|
|
98
|
+
logger: loggerOptions
|
|
99
|
+
};
|
|
100
|
+
app = new import_server.Application(applicationOptions);
|
|
101
|
+
app.logger.info("[worker] app boot");
|
|
102
|
+
import_utils.Container.reset();
|
|
103
|
+
import_utils.Container.set({ id: "db", value: app.db });
|
|
104
|
+
import_utils.Container.set({ id: "app", value: app });
|
|
105
|
+
import_utils.Container.set({ id: "logger", value: app.logger });
|
|
106
|
+
await loadPlugins(app);
|
|
107
|
+
app.logger.info("[worker] app has been started");
|
|
108
|
+
handleWorkerMessages(app);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
(_a = app == null ? void 0 : app.logger) == null ? void 0 : _a.error("[worker] Failed to start worker:", err);
|
|
111
|
+
try {
|
|
112
|
+
await ((_b = app == null ? void 0 : app.db) == null ? void 0 : _b.close());
|
|
113
|
+
} catch (cleanupErr) {
|
|
114
|
+
(_c = app == null ? void 0 : app.logger) == null ? void 0 : _c.error("[worker] Cleanup failed:", cleanupErr);
|
|
115
|
+
}
|
|
116
|
+
process.exit(-1);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
async function loadPlugins(app) {
|
|
120
|
+
await app.pm.initPlugins();
|
|
121
|
+
const userPluginName = app.pm.get(import_module_user.default).name;
|
|
122
|
+
const loadPlugins2 = [userPluginName];
|
|
123
|
+
for (const [P, plugin] of app.pm.getPlugins()) {
|
|
124
|
+
if (plugin.name.startsWith("field-")) {
|
|
125
|
+
loadPlugins2.push(plugin.name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
loadPlugins2.push(app.pm.get(import_module_collection.default).name);
|
|
129
|
+
for (const pluginName of loadPlugins2) {
|
|
130
|
+
const plugin = app.pm.get(pluginName);
|
|
131
|
+
try {
|
|
132
|
+
await plugin.beforeLoad();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
app.logger.error(`Failed to execute beforeLoad for plugin ${pluginName}:`, error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const [P, plugin] of app.pm.getPlugins()) {
|
|
139
|
+
if (!plugin.enabled) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
await plugin.loadCollections();
|
|
143
|
+
if (!loadPlugins2.includes(plugin.name)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
await plugin.load();
|
|
147
|
+
for (const feature of plugin.featureInstances) {
|
|
148
|
+
await feature.load();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
await app.db.getRepository("collections").load();
|
|
152
|
+
}
|
|
153
|
+
main();
|
|
154
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
155
|
+
0 && (module.exports = {
|
|
156
|
+
main
|
|
157
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Application } from '@tachybase/server';
|
|
2
|
+
import { WorkerEventInputPluginMethod } from './workerTypes';
|
|
3
|
+
export declare class WorkerManager {
|
|
4
|
+
app: Application;
|
|
5
|
+
get available(): boolean;
|
|
6
|
+
private isProd;
|
|
7
|
+
private workerNum;
|
|
8
|
+
private workerList;
|
|
9
|
+
private currentWorkerIndex;
|
|
10
|
+
private busyWorkers;
|
|
11
|
+
private busyWorkerSet;
|
|
12
|
+
private cache;
|
|
13
|
+
private errorRecoveryTimes;
|
|
14
|
+
private static readonly timeoutSecond;
|
|
15
|
+
private getPluginMethodKey;
|
|
16
|
+
private getGlobalKey;
|
|
17
|
+
private databaseOptions;
|
|
18
|
+
constructor(app: Application, workerNum?: number);
|
|
19
|
+
getPresetWorkerNum(): number;
|
|
20
|
+
getCurrentWorkerNum(): number;
|
|
21
|
+
getBusyWorkerNum(): number;
|
|
22
|
+
initWorkers(): Promise<void>;
|
|
23
|
+
private addWorker;
|
|
24
|
+
clear(): Promise<void>;
|
|
25
|
+
resetWorkerNum(targetNum: number): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* 检查并更新并发计数
|
|
28
|
+
* @param key - 用于缓存的键
|
|
29
|
+
* @param limit - 并发限制
|
|
30
|
+
* @param cache - 缓存对象
|
|
31
|
+
* @returns 是否超过并发限制
|
|
32
|
+
*/
|
|
33
|
+
private checkAndUpdateConcurrency;
|
|
34
|
+
/**
|
|
35
|
+
* 减少并发计数
|
|
36
|
+
* @param key - 用于缓存的键
|
|
37
|
+
* @param cache - 缓存对象
|
|
38
|
+
*/
|
|
39
|
+
private decrementConcurrencyCount;
|
|
40
|
+
/**
|
|
41
|
+
* 运行插件方法
|
|
42
|
+
*/
|
|
43
|
+
callPluginMethod(values: WorkerEventInputPluginMethod): Promise<unknown>;
|
|
44
|
+
private handleWorkerCompletion;
|
|
45
|
+
/**
|
|
46
|
+
* 运行通用方法
|
|
47
|
+
*/
|
|
48
|
+
private callMethod;
|
|
49
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var workerManager_exports = {};
|
|
29
|
+
__export(workerManager_exports, {
|
|
30
|
+
WorkerManager: () => WorkerManager
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(workerManager_exports);
|
|
33
|
+
var import_crypto = require("crypto");
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
var import_worker_threads = require("worker_threads");
|
|
36
|
+
var import_utils = require("@tachybase/utils");
|
|
37
|
+
var import_constants = require("./constants");
|
|
38
|
+
var import_workerTypes = require("./workerTypes");
|
|
39
|
+
function copyBasicTypes(obj) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const key in obj) {
|
|
42
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
43
|
+
const value = obj[key];
|
|
44
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
45
|
+
result[key] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
class WorkerManager {
|
|
52
|
+
constructor(app, workerNum) {
|
|
53
|
+
this.app = app;
|
|
54
|
+
if (!import_worker_threads.isMainThread) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (workerNum !== void 0) {
|
|
58
|
+
this.workerNum = workerNum;
|
|
59
|
+
}
|
|
60
|
+
this.databaseOptions = copyBasicTypes(app.db.options);
|
|
61
|
+
}
|
|
62
|
+
get available() {
|
|
63
|
+
return this.workerList.length > 0;
|
|
64
|
+
}
|
|
65
|
+
isProd = true;
|
|
66
|
+
workerNum = 0;
|
|
67
|
+
workerList = [];
|
|
68
|
+
currentWorkerIndex = 0;
|
|
69
|
+
busyWorkers = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
busyWorkerSet = /* @__PURE__ */ new Set();
|
|
71
|
+
cache = /* @__PURE__ */ new Map();
|
|
72
|
+
errorRecoveryTimes = 0;
|
|
73
|
+
// 默认超时时间
|
|
74
|
+
static timeoutSecond = import_constants.WORKER_TIMEOUT;
|
|
75
|
+
getPluginMethodKey(values) {
|
|
76
|
+
return `worker:pluginMethod:${values.plugin}:${values.method}`;
|
|
77
|
+
}
|
|
78
|
+
getGlobalKey() {
|
|
79
|
+
return `worker:global`;
|
|
80
|
+
}
|
|
81
|
+
databaseOptions;
|
|
82
|
+
getPresetWorkerNum() {
|
|
83
|
+
return this.workerNum;
|
|
84
|
+
}
|
|
85
|
+
getCurrentWorkerNum() {
|
|
86
|
+
return this.workerList.length;
|
|
87
|
+
}
|
|
88
|
+
getBusyWorkerNum() {
|
|
89
|
+
return this.busyWorkerSet.size;
|
|
90
|
+
}
|
|
91
|
+
async initWorkers() {
|
|
92
|
+
if (!import_worker_threads.isMainThread) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.isProd = await (0, import_utils.fsExists)(import_path.default.resolve(__dirname, `${import_constants.WORKER_FILE}.js`));
|
|
96
|
+
await Promise.all(Array.from({ length: this.workerNum }).map(() => this.addWorker()));
|
|
97
|
+
}
|
|
98
|
+
async addWorker() {
|
|
99
|
+
let worker;
|
|
100
|
+
if (this.isProd) {
|
|
101
|
+
worker = new import_worker_threads.Worker(import_path.default.resolve(__dirname, `${import_constants.WORKER_FILE}.js`), {
|
|
102
|
+
workerData: {
|
|
103
|
+
appName: this.app.name,
|
|
104
|
+
databaseOptions: this.databaseOptions
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
worker = new import_worker_threads.Worker(import_path.default.resolve(__dirname, "../../worker-starter.mjs"), {
|
|
109
|
+
workerData: {
|
|
110
|
+
scriptPath: import_path.default.resolve(__dirname, `${import_constants.WORKER_FILE}.ts`),
|
|
111
|
+
appName: this.app.name,
|
|
112
|
+
databaseOptions: this.databaseOptions
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
worker.on("message", (message) => {
|
|
118
|
+
if (message.event === "started") {
|
|
119
|
+
this.workerList.push(worker);
|
|
120
|
+
resolve();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
worker.on("error", (error) => {
|
|
124
|
+
reject(error);
|
|
125
|
+
});
|
|
126
|
+
worker.on("exit", async (code) => {
|
|
127
|
+
if (code === 0 || code === 1) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.errorRecoveryTimes++;
|
|
131
|
+
this.app.logger.error(`Worker stopped with exit code ${code}, times: ${this.errorRecoveryTimes}`);
|
|
132
|
+
if (this.errorRecoveryTimes >= import_constants.WORKER_ERROR_RETRY) {
|
|
133
|
+
this.app.logger.error(`Worker error for ${this.errorRecoveryTimes} times`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.workerList = this.workerList.filter((w) => w !== worker);
|
|
137
|
+
if (this.workerList.length < this.workerNum) {
|
|
138
|
+
await this.addWorker();
|
|
139
|
+
}
|
|
140
|
+
this.errorRecoveryTimes = 0;
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async clear() {
|
|
145
|
+
if (!import_worker_threads.isMainThread) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await Promise.all(this.workerList.map((worker) => worker.terminate()));
|
|
149
|
+
this.workerList = [];
|
|
150
|
+
this.workerNum = 0;
|
|
151
|
+
this.currentWorkerIndex = 0;
|
|
152
|
+
this.busyWorkers = /* @__PURE__ */ new WeakMap();
|
|
153
|
+
this.busyWorkerSet = /* @__PURE__ */ new Set();
|
|
154
|
+
this.errorRecoveryTimes = 0;
|
|
155
|
+
this.cache.clear();
|
|
156
|
+
}
|
|
157
|
+
async resetWorkerNum(targetNum) {
|
|
158
|
+
var _a;
|
|
159
|
+
if (!import_worker_threads.isMainThread) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.workerNum = targetNum;
|
|
163
|
+
if (targetNum < this.workerList.length) {
|
|
164
|
+
const excessWorkers = this.workerList.length - targetNum;
|
|
165
|
+
let terminatedCount = 0;
|
|
166
|
+
for (let i = this.workerList.length - 1; i >= 0 && terminatedCount < excessWorkers; i--) {
|
|
167
|
+
const worker = this.workerList[i];
|
|
168
|
+
if (!((_a = this.busyWorkers.get(worker)) == null ? void 0 : _a.size)) {
|
|
169
|
+
this.workerList.splice(i, 1);
|
|
170
|
+
worker.terminate();
|
|
171
|
+
terminatedCount++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else if (targetNum > this.workerList.length) {
|
|
175
|
+
const additionalWorkers = targetNum - this.workerList.length;
|
|
176
|
+
for (let i = 0; i < additionalWorkers; i++) {
|
|
177
|
+
this.addWorker();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 检查并更新并发计数
|
|
183
|
+
* @param key - 用于缓存的键
|
|
184
|
+
* @param limit - 并发限制
|
|
185
|
+
* @param cache - 缓存对象
|
|
186
|
+
* @returns 是否超过并发限制
|
|
187
|
+
*/
|
|
188
|
+
checkAndUpdateConcurrency(key, limit, cache) {
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
let countInfo = cache.get(key);
|
|
191
|
+
if (countInfo && countInfo.lastTime) {
|
|
192
|
+
const diff = now - countInfo.lastTime.getTime();
|
|
193
|
+
if (diff > 1e3 * WorkerManager.timeoutSecond) {
|
|
194
|
+
countInfo.count = 0;
|
|
195
|
+
this.app.logger.debug(`[worker] Reset concurrency count for ${key} due to timeout`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (countInfo && countInfo.count >= limit) {
|
|
199
|
+
this.app.logger.debug(`[worker] Concurrency limit reached for ${key}: ${countInfo.count}/${limit}`);
|
|
200
|
+
return true;
|
|
201
|
+
} else {
|
|
202
|
+
if (!countInfo) {
|
|
203
|
+
countInfo = {
|
|
204
|
+
lastTime: new Date(now),
|
|
205
|
+
count: 1
|
|
206
|
+
};
|
|
207
|
+
} else {
|
|
208
|
+
countInfo.lastTime = new Date(now);
|
|
209
|
+
countInfo.count++;
|
|
210
|
+
}
|
|
211
|
+
cache.set(key, countInfo);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 减少并发计数
|
|
217
|
+
* @param key - 用于缓存的键
|
|
218
|
+
* @param cache - 缓存对象
|
|
219
|
+
*/
|
|
220
|
+
decrementConcurrencyCount(key, cache) {
|
|
221
|
+
const countInfo = cache.get(key);
|
|
222
|
+
if (countInfo) {
|
|
223
|
+
countInfo.count--;
|
|
224
|
+
if (countInfo.count < 0) {
|
|
225
|
+
cache.delete(key);
|
|
226
|
+
} else {
|
|
227
|
+
countInfo.lastTime = /* @__PURE__ */ new Date();
|
|
228
|
+
cache.set(key, countInfo);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 运行插件方法
|
|
234
|
+
*/
|
|
235
|
+
async callPluginMethod(values) {
|
|
236
|
+
if (values.globalConcurrency) {
|
|
237
|
+
const exceeded = this.checkAndUpdateConcurrency(this.getGlobalKey(), values.globalConcurrency, this.cache);
|
|
238
|
+
if (exceeded) {
|
|
239
|
+
throw new Error("Global method call limit exceeded");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (values.concurrency) {
|
|
243
|
+
const exceeded = this.checkAndUpdateConcurrency(this.getPluginMethodKey(values), values.concurrency, this.cache);
|
|
244
|
+
if (exceeded) {
|
|
245
|
+
throw new Error("Plugin method call limit exceeded");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const result = await this.callMethod(import_workerTypes.WorkerEvent.PluginMethod, {
|
|
249
|
+
...values,
|
|
250
|
+
plugin: this.app.pm.get(values.plugin).name
|
|
251
|
+
});
|
|
252
|
+
if (values.globalConcurrency) {
|
|
253
|
+
this.decrementConcurrencyCount(this.getGlobalKey(), this.cache);
|
|
254
|
+
}
|
|
255
|
+
if (values.concurrency) {
|
|
256
|
+
this.decrementConcurrencyCount(this.getPluginMethodKey(values), this.cache);
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
handleWorkerCompletion(worker, reqId, resolve, result, reject, error) {
|
|
261
|
+
const busySet = this.busyWorkers.get(worker);
|
|
262
|
+
if (busySet) {
|
|
263
|
+
busySet.delete(reqId);
|
|
264
|
+
if (!busySet.size) {
|
|
265
|
+
this.busyWorkers.delete(worker);
|
|
266
|
+
this.busyWorkerSet.delete(worker);
|
|
267
|
+
if (this.workerList.length > this.workerNum) {
|
|
268
|
+
this.workerList = this.workerList.filter((w) => w !== worker);
|
|
269
|
+
worker.terminate();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (error) {
|
|
274
|
+
reject(error);
|
|
275
|
+
} else {
|
|
276
|
+
resolve(result);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 运行通用方法
|
|
281
|
+
*/
|
|
282
|
+
async callMethod(workerEvent, values) {
|
|
283
|
+
if (this.workerList.length === 0) {
|
|
284
|
+
throw new Error("No available workers");
|
|
285
|
+
}
|
|
286
|
+
const reqId = (0, import_crypto.randomUUID)();
|
|
287
|
+
if (this.currentWorkerIndex >= this.workerList.length) {
|
|
288
|
+
this.currentWorkerIndex = 0;
|
|
289
|
+
}
|
|
290
|
+
const worker = this.workerList[this.currentWorkerIndex];
|
|
291
|
+
this.currentWorkerIndex = (this.currentWorkerIndex + 1) % this.workerList.length;
|
|
292
|
+
if (!this.busyWorkers.has(worker)) {
|
|
293
|
+
this.busyWorkers.set(worker, /* @__PURE__ */ new Set());
|
|
294
|
+
this.busyWorkerSet.add(worker);
|
|
295
|
+
}
|
|
296
|
+
this.busyWorkers.get(worker).add(reqId);
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
let timeout;
|
|
299
|
+
let handleMessage;
|
|
300
|
+
let handleError;
|
|
301
|
+
handleMessage = (message) => {
|
|
302
|
+
if (message.reqId === reqId) {
|
|
303
|
+
clearTimeout(timeout);
|
|
304
|
+
worker.off("message", handleMessage);
|
|
305
|
+
worker.off("error", handleError);
|
|
306
|
+
this.handleWorkerCompletion(worker, reqId, resolve, message.result);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
handleError = (error) => {
|
|
310
|
+
clearTimeout(timeout);
|
|
311
|
+
worker.off("message", handleMessage);
|
|
312
|
+
worker.off("error", handleError);
|
|
313
|
+
this.handleWorkerCompletion(worker, reqId, resolve, void 0, reject, error);
|
|
314
|
+
};
|
|
315
|
+
timeout = setTimeout(
|
|
316
|
+
() => {
|
|
317
|
+
worker.off("message", handleMessage);
|
|
318
|
+
worker.off("error", handleError);
|
|
319
|
+
this.handleWorkerCompletion(
|
|
320
|
+
worker,
|
|
321
|
+
reqId,
|
|
322
|
+
resolve,
|
|
323
|
+
void 0,
|
|
324
|
+
reject,
|
|
325
|
+
new Error("callPluginMethod timed out")
|
|
326
|
+
);
|
|
327
|
+
},
|
|
328
|
+
(values.timeoutSecond || WorkerManager.timeoutSecond) * 1e3
|
|
329
|
+
);
|
|
330
|
+
worker.on("message", handleMessage);
|
|
331
|
+
worker.on("error", handleError);
|
|
332
|
+
worker.postMessage({
|
|
333
|
+
reqId,
|
|
334
|
+
event: workerEvent,
|
|
335
|
+
values
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
341
|
+
0 && (module.exports = {
|
|
342
|
+
WorkerManager
|
|
343
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Constructor, Plugin } from '@tachybase/server';
|
|
2
|
+
export declare enum WorkerEvent {
|
|
3
|
+
Started = "started",
|
|
4
|
+
PluginMethod = "PluginMethod"
|
|
5
|
+
}
|
|
6
|
+
export type WorkerEventInputDefault = {
|
|
7
|
+
timeoutSecond?: number;
|
|
8
|
+
concurrency?: number;
|
|
9
|
+
globalConcurrency?: number;
|
|
10
|
+
};
|
|
11
|
+
export type WorkerEventInputPluginMethod<T = any> = WorkerEventInputDefault & {
|
|
12
|
+
plugin: Constructor<Plugin> | string;
|
|
13
|
+
method: string;
|
|
14
|
+
params: T;
|
|
15
|
+
};
|
|
16
|
+
export type WorkerEventInput = WorkerEventInputPluginMethod;
|
|
17
|
+
export type WorkerWebInfo = {
|
|
18
|
+
preset: number;
|
|
19
|
+
current: number;
|
|
20
|
+
busy: number;
|
|
21
|
+
};
|
|
22
|
+
export type callPluginMethodInfo = {
|
|
23
|
+
lastTime: Date;
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var workerTypes_exports = {};
|
|
19
|
+
__export(workerTypes_exports, {
|
|
20
|
+
WorkerEvent: () => WorkerEvent
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(workerTypes_exports);
|
|
23
|
+
var WorkerEvent = /* @__PURE__ */ ((WorkerEvent2) => {
|
|
24
|
+
WorkerEvent2["Started"] = "started";
|
|
25
|
+
WorkerEvent2["PluginMethod"] = "PluginMethod";
|
|
26
|
+
return WorkerEvent2;
|
|
27
|
+
})(WorkerEvent || {});
|
|
28
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
29
|
+
0 && (module.exports = {
|
|
30
|
+
WorkerEvent
|
|
31
|
+
});
|