@coherentglobal/wasm-runner 0.1.19 → 0.3.2
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 +16 -0
- package/dist/CancellationToken.js +2 -0
- package/dist/CancellationToken.js.map +1 -1
- package/dist/browser/logger.js +6 -2
- package/dist/browser/logger.js.map +1 -1
- package/dist/browser/template/worker.template.js +113 -105
- package/dist/browser/template/worker.template.js.map +1 -1
- package/dist/browser/template.js +1 -1
- package/dist/browser.d.ts +10 -3
- package/dist/browser.js +255 -257
- package/dist/browser.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/defaultExternalResolver.js +6 -15
- package/dist/defaultExternalResolver.js.map +1 -1
- package/dist/error.js +10 -2
- package/dist/error.js.map +1 -1
- package/dist/node/logger.d.ts +2 -1
- package/dist/node/logger.js +1 -1
- package/dist/node/logger.js.map +1 -1
- package/dist/node/logger.ts +1 -1
- package/dist/node/mockLogger.d.ts +2 -1
- package/dist/node/template/main.template.ejs +60 -29
- package/dist/node/threads/mockWorkerThread.d.ts +1 -0
- package/dist/node/threads/mockWorkerThread.js +10 -3
- package/dist/node/threads/mockWorkerThread.js.map +1 -1
- package/dist/node/threads/workerPool.d.ts +2 -1
- package/dist/node/threads/workerPool.js +7 -6
- package/dist/node/threads/workerPool.js.map +1 -1
- package/dist/node/threads/workerPool.ts +10 -7
- package/dist/node/threads/workerThread.d.ts +1 -2
- package/dist/node/threads/workerThread.js +35 -42
- package/dist/node/threads/workerThread.js.map +1 -1
- package/dist/node/threads/workerThread.ts +8 -12
- package/dist/node.d.ts +26 -8
- package/dist/node.js +596 -437
- package/dist/node.js.map +1 -1
- package/dist/responseTimeMetric.d.ts +7 -3
- package/dist/responseTimeMetric.js +131 -28
- package/dist/responseTimeMetric.js.map +1 -1
- package/dist/serializer/columnarSerializer.d.ts +11 -3
- package/dist/serializer/columnarSerializer.js +49 -29
- package/dist/serializer/columnarSerializer.js.map +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +3 -2
- package/dist/utils.js +38 -48
- package/dist/utils.js.map +1 -1
- package/package.json +70 -59
package/dist/node.js
CHANGED
|
@@ -15,35 +15,30 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
34
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
37
|
};
|
|
37
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
39
|
exports.WasmRunner = exports.ModelExecuteCancelled = exports.CancellationToken = exports.ColumnarSerializer = void 0;
|
|
39
|
-
/* eslint-disable consistent-return */
|
|
40
|
-
/* eslint-disable no-shadow */
|
|
41
|
-
/* eslint-disable camelcase */
|
|
42
|
-
/* eslint-disable no-param-reassign */
|
|
43
|
-
/* eslint-disable no-underscore-dangle */
|
|
44
40
|
const tmp_1 = __importDefault(require("tmp"));
|
|
45
41
|
const cuid2_1 = require("@paralleldrive/cuid2");
|
|
46
|
-
const logger_1 = __importDefault(require("./node/logger"));
|
|
47
42
|
const unzipper_1 = require("unzipper");
|
|
48
43
|
const ejs_1 = __importDefault(require("ejs"));
|
|
49
44
|
const stream_1 = __importStar(require("stream"));
|
|
@@ -52,13 +47,15 @@ const path_1 = __importDefault(require("path"));
|
|
|
52
47
|
const util_1 = require("util");
|
|
53
48
|
const got_1 = __importDefault(require("got"));
|
|
54
49
|
const semver_1 = __importDefault(require("semver"));
|
|
50
|
+
const events_1 = __importDefault(require("events"));
|
|
51
|
+
const node_os_1 = require("node:os");
|
|
55
52
|
const workerPool_1 = __importDefault(require("./node/threads/workerPool"));
|
|
56
53
|
const utils = __importStar(require("./utils"));
|
|
57
54
|
const types_1 = require("./types");
|
|
58
55
|
const error_1 = __importDefault(require("./error"));
|
|
59
|
-
const node_os_1 = require("node:os");
|
|
60
56
|
const constants_1 = require("./constants");
|
|
61
57
|
const responseTimeMetric_1 = __importDefault(require("./responseTimeMetric"));
|
|
58
|
+
const logger_1 = __importDefault(require("./node/logger"));
|
|
62
59
|
/* istanbul ignore next */
|
|
63
60
|
var columnarSerializer_1 = require("./serializer/columnarSerializer");
|
|
64
61
|
Object.defineProperty(exports, "ColumnarSerializer", { enumerable: true, get: function () { return columnarSerializer_1.ColumnarSerializer; } });
|
|
@@ -66,17 +63,25 @@ var CancellationToken_1 = require("./CancellationToken");
|
|
|
66
63
|
Object.defineProperty(exports, "CancellationToken", { enumerable: true, get: function () { return CancellationToken_1.CancellationToken; } });
|
|
67
64
|
var error_2 = require("./error");
|
|
68
65
|
Object.defineProperty(exports, "ModelExecuteCancelled", { enumerable: true, get: function () { return error_2.ModelExecuteCancelled; } });
|
|
66
|
+
// Increase max listeners to handle multiple WasmRunner instances
|
|
67
|
+
// This is needed because tmp.setGracefulCleanup() registers SIGINT handlers
|
|
68
|
+
if (typeof process !== "undefined" && process.setMaxListeners) {
|
|
69
|
+
process.setMaxListeners(0); // 0 means unlimited
|
|
70
|
+
}
|
|
71
|
+
// Only call setGracefulCleanup once to avoid multiple SIGINT listeners
|
|
72
|
+
if (!global.__tmpCleanupInitialized) {
|
|
73
|
+
tmp_1.default.setGracefulCleanup();
|
|
74
|
+
global.__tmpCleanupInitialized = true;
|
|
75
|
+
}
|
|
69
76
|
const platformUsed = (0, node_os_1.platform)();
|
|
70
77
|
const pipeline = (0, util_1.promisify)(stream_1.default.pipeline);
|
|
71
|
-
function replaceRegexInFile(file, search, replace) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return true;
|
|
79
|
-
});
|
|
78
|
+
async function replaceRegexInFile(file, search, replace) {
|
|
79
|
+
const contents = await fs_1.default.promises.readFile(file, "utf8");
|
|
80
|
+
const replaced_contents = contents.replace(search, replace);
|
|
81
|
+
const tmpfile = `${file}-${(0, cuid2_1.createId)()}.jstmpreplace`;
|
|
82
|
+
await fs_1.default.promises.writeFile(tmpfile, replaced_contents, "utf8");
|
|
83
|
+
await fs_1.default.promises.rename(tmpfile, file);
|
|
84
|
+
return true;
|
|
80
85
|
}
|
|
81
86
|
/* istanbul ignore next */
|
|
82
87
|
const toWindowsPath = (fileLocation) => fileLocation.split(path_1.default.sep).join("//");
|
|
@@ -97,7 +102,6 @@ const unzipFiles = (id, url, workerFolder) => {
|
|
|
97
102
|
let metadata;
|
|
98
103
|
let formspec;
|
|
99
104
|
let compilerLog;
|
|
100
|
-
// @ts-ignore
|
|
101
105
|
const stream = fs_1.default.createReadStream(url).pipe((0, unzipper_1.Parse)());
|
|
102
106
|
return new Promise((resolve, reject) => {
|
|
103
107
|
stream.on("entry", (entry) => {
|
|
@@ -149,62 +153,73 @@ const template = ejs_1.default.compile(fs_1.default.readFileSync(path_1.default.
|
|
|
149
153
|
encoding: "utf8",
|
|
150
154
|
flag: "r",
|
|
151
155
|
}), { async: false });
|
|
152
|
-
const delay = (time) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
};
|
|
156
|
+
const delay = (time) => new Promise((res) => {
|
|
157
|
+
setTimeout(res, time);
|
|
158
|
+
});
|
|
157
159
|
/**
|
|
158
160
|
* WasmRunner class, responsible for managing model and it's lifecycle
|
|
159
161
|
*
|
|
160
162
|
* @class
|
|
161
163
|
* @classdesc WasmRunner class, responsible for managing model and it's lifecycle
|
|
162
164
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
class WasmRunner extends events_1.default {
|
|
166
|
+
_tempWorkerFolder;
|
|
167
|
+
_tempModelFolder;
|
|
168
|
+
license;
|
|
169
|
+
models;
|
|
170
|
+
externalResolverModule;
|
|
171
|
+
initializingModels;
|
|
172
|
+
options;
|
|
173
|
+
safeCompiler;
|
|
174
|
+
logger;
|
|
165
175
|
/**
|
|
166
176
|
* Create new Wasm Runner instance
|
|
167
177
|
*
|
|
168
178
|
* @param {RunnerConfig} wasmRunnerConfig
|
|
169
179
|
*/
|
|
170
180
|
constructor(wasmRunnerConfig, externalResolverModule = "", options = {}, license = "") {
|
|
181
|
+
super();
|
|
171
182
|
let baseTmpDir = null;
|
|
172
183
|
if (process.env.DEV_DEBUG_TMP_PATH) {
|
|
173
184
|
baseTmpDir = path_1.default.join(process.cwd(), process.env.DEV_DEBUG_TMP_PATH);
|
|
174
185
|
}
|
|
186
|
+
if (options.tmpDir) {
|
|
187
|
+
baseTmpDir = options.tmpDir;
|
|
188
|
+
}
|
|
175
189
|
this._tempWorkerFolder = tmp_1.default.dirSync({
|
|
176
190
|
prefix: "wasmserver_worker",
|
|
177
|
-
// @ts-ignore
|
|
178
191
|
mode: 0o750,
|
|
179
192
|
tmpdir: baseTmpDir,
|
|
180
193
|
});
|
|
181
194
|
this._tempModelFolder = tmp_1.default.dirSync({
|
|
182
195
|
prefix: "wasmserver_model",
|
|
183
|
-
// @ts-ignore
|
|
184
196
|
mode: 0o750,
|
|
185
197
|
tmpdir: baseTmpDir,
|
|
186
198
|
});
|
|
187
199
|
this.license = license;
|
|
188
|
-
this.initializingModels =
|
|
200
|
+
this.initializingModels = new Set();
|
|
201
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
189
202
|
this.externalResolverModule = externalResolverModule
|
|
190
203
|
? require(useCorrectPath(require.resolve(externalResolverModule)))
|
|
191
204
|
: require(require.resolve("./defaultExternalResolver"));
|
|
192
|
-
|
|
205
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
206
|
+
this.models = new Map();
|
|
193
207
|
this.options = options;
|
|
208
|
+
this.logger = this.options.logger || logger_1.default;
|
|
194
209
|
if (!utils.isEmpty(wasmRunnerConfig)) {
|
|
195
210
|
if (Array.isArray(wasmRunnerConfig)) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
wasmRunnerConfig.forEach((wsconfig) => {
|
|
212
|
+
this.models.set(wsconfig.id, {
|
|
213
|
+
id: wsconfig.id,
|
|
214
|
+
url: wsconfig.url,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
200
217
|
}
|
|
201
218
|
else if (typeof wasmRunnerConfig === "object") {
|
|
202
|
-
this.models
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
},
|
|
207
|
-
];
|
|
219
|
+
this.models.set(wasmRunnerConfig.id, {
|
|
220
|
+
id: wasmRunnerConfig.id,
|
|
221
|
+
url: wasmRunnerConfig.url,
|
|
222
|
+
});
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
}
|
|
@@ -215,247 +230,302 @@ class WasmRunner {
|
|
|
215
230
|
* @function initialize
|
|
216
231
|
*/
|
|
217
232
|
/* istanbul ignore next */
|
|
218
|
-
initialize() {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
yield Promise.all(this.models.map((m) => __awaiter(this, void 0, void 0, function* () {
|
|
224
|
-
return this._initializeModelInstance(m);
|
|
225
|
-
})));
|
|
226
|
-
});
|
|
233
|
+
async initialize(license = "") {
|
|
234
|
+
if (this.options.compatibilityCheckEnabled) {
|
|
235
|
+
this.safeCompiler = await this.getSafeCompilerVersion(this.options.runnerVersion);
|
|
236
|
+
}
|
|
237
|
+
await Promise.all(Array.from(this.models.values()).map(async (m) => this._initializeModelInstance(m)));
|
|
227
238
|
}
|
|
228
239
|
/* istanbul ignore next */
|
|
229
|
-
_createModelInstance(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
240
|
+
async _createModelInstance(id, url, workerFolder, size = 1) {
|
|
241
|
+
const workerSize = size || 1;
|
|
242
|
+
this.logger.debug({
|
|
243
|
+
TimeStamp: Date.now(),
|
|
244
|
+
EventType: "Runner._createModelInstance",
|
|
245
|
+
size: workerSize,
|
|
246
|
+
ModelId: id,
|
|
247
|
+
});
|
|
248
|
+
const modelPack = await unzipFiles(id, url, workerFolder);
|
|
249
|
+
this.logger.debug({
|
|
250
|
+
TimeStamp: Date.now(),
|
|
251
|
+
EventType: "Runner.UnZipFiles",
|
|
252
|
+
ModelId: id,
|
|
253
|
+
});
|
|
254
|
+
const modelName = id;
|
|
255
|
+
await replaceRegexInFile(modelPack.js, `wasmBinaryFile="${path_1.default.basename(modelPack.wasm)}"`, `scriptDirectory="";wasmBinaryFile="${modelPack.wasm}"`);
|
|
256
|
+
const replaceIsFileURLFunc = "function isFileURI(filename) { if(typeof process !== undefined && process.versions && process.versions.node) { return true; }";
|
|
257
|
+
await replaceRegexInFile(modelPack.js, "function isFileURI(filename) {", replaceIsFileURLFunc);
|
|
258
|
+
await replaceRegexInFile(modelPack.js, "function isFileURI(filename){", replaceIsFileURLFunc);
|
|
259
|
+
// Remove `new URL()` since `isFileURI` will always be true for Node
|
|
260
|
+
await replaceRegexInFile(modelPack.js, /\s*isFileURI\s*\(\s*filename\s*\)\s*\?\s*new\s*URL\s*\(\s*filename\s*\)\s*:\s*/g, "");
|
|
261
|
+
if (modelPack.data) {
|
|
262
|
+
await replaceRegexInFile(modelPack.js, `"${path_1.default.basename(modelPack.data)}"`, `"${modelPack.data}"`);
|
|
263
|
+
await replaceRegexInFile(modelPack.js, `"${path_1.default.basename(modelPack.data)}"`, `"${modelPack.data}"`);
|
|
264
|
+
await replaceRegexInFile(modelPack.js, `"${path_1.default.basename(modelPack.data)}"`, `"${modelPack.data}"`);
|
|
265
|
+
await replaceRegexInFile(modelPack.js, `"${path_1.default.basename(modelPack.data)}"`, `"${modelPack.data}"`);
|
|
266
|
+
}
|
|
267
|
+
// Prepare File Paths
|
|
268
|
+
// const externalResolverPath = useCorrectPath(this.externalResolverModule);
|
|
269
|
+
const loggerPath = useCorrectPath(process.env.NODE_ENV === "test"
|
|
270
|
+
? require.resolve("./node/mockLogger.js")
|
|
271
|
+
: require.resolve("./node/logger"));
|
|
272
|
+
const workerThread = useCorrectPath(process.env.NODE_ENV === "test"
|
|
273
|
+
? require.resolve("./node/threads/mockWorkerThread.js")
|
|
274
|
+
: require.resolve("./node/threads/workerThread"));
|
|
275
|
+
let metadata = "undefined";
|
|
276
|
+
if (!utils.isEmpty(modelPack.metadata)) {
|
|
277
|
+
const metadataPath = useCorrectPath(require.resolve(modelPack.metadata));
|
|
278
|
+
metadata = `require("${metadataPath}")`;
|
|
279
|
+
}
|
|
280
|
+
let formspec = "undefined";
|
|
281
|
+
if (!utils.isEmpty(modelPack.formspec)) {
|
|
282
|
+
const formSpecPath = useCorrectPath(require.resolve(modelPack.formspec));
|
|
283
|
+
formspec = `require("${formSpecPath}")`;
|
|
284
|
+
}
|
|
285
|
+
let compilerVersion = "undefined";
|
|
286
|
+
let compatibilityStatus = "undefined";
|
|
287
|
+
compilerVersion = !utils.isEmpty(modelPack.compilerLog)
|
|
288
|
+
? await this.getNeuronCompilerVersion(`"${modelPack.compilerLog}"`)
|
|
289
|
+
: undefined;
|
|
290
|
+
if (this.options.compatibilityCheckEnabled) {
|
|
291
|
+
compatibilityStatus =
|
|
292
|
+
await this.checkCompilerVersionCompatibility(compilerVersion);
|
|
293
|
+
}
|
|
294
|
+
const fileContent = template({
|
|
295
|
+
runtime: modelPack.js.replace(".js", ""),
|
|
296
|
+
model: modelPack.wasm,
|
|
297
|
+
metadata,
|
|
298
|
+
formspec,
|
|
299
|
+
modelName,
|
|
300
|
+
modules: {
|
|
301
|
+
logger: loggerPath,
|
|
302
|
+
workerThread: workerThread,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
const workerPath = path_1.default.join(workerFolder, `worker-${id}.js`);
|
|
306
|
+
await fs_1.default.promises.writeFile(workerPath, fileContent);
|
|
307
|
+
this.logger.debug({
|
|
308
|
+
TimeStamp: Date.now(),
|
|
309
|
+
EventType: "Runner.CreateTemplates",
|
|
310
|
+
ModelId: id,
|
|
311
|
+
});
|
|
312
|
+
let workerPool;
|
|
313
|
+
let reportedStats = 0;
|
|
314
|
+
/**
|
|
315
|
+
* Processes the requests from Worker thread. Interprets the request
|
|
316
|
+
* `type` and implement necessary logic and returns the result.
|
|
317
|
+
*
|
|
318
|
+
* @param {Object} requestData
|
|
319
|
+
* Event details from Worker thread.
|
|
320
|
+
*/
|
|
321
|
+
const processWorkerThreadRequest = async (requestData) => {
|
|
322
|
+
const { type, payload, context, threadId } = requestData;
|
|
323
|
+
// Worker request of model execution on main thread.
|
|
324
|
+
if (type === constants_1.THREAD_EVENT_TYPES.REQUEST_EXECUTE) {
|
|
325
|
+
try {
|
|
326
|
+
// Callback the resolver function
|
|
327
|
+
const result = await this.externalResolverModule.sparkService(payload, context);
|
|
328
|
+
// Send back the response to worker thread
|
|
329
|
+
workerPool.postMessage({
|
|
330
|
+
payload: result,
|
|
331
|
+
type: constants_1.THREAD_EVENT_TYPES.RESPONSE,
|
|
332
|
+
threadId,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
// Send back the error to worker thread
|
|
337
|
+
workerPool.postMessage({
|
|
338
|
+
payload: e,
|
|
339
|
+
type: constants_1.THREAD_EVENT_TYPES.ERROR,
|
|
340
|
+
threadId,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
289
343
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const processWorkerThreadRequest = (requestData) => __awaiter(this, void 0, void 0, function* () {
|
|
317
|
-
const { type, payload, context, threadId } = requestData;
|
|
318
|
-
// Worker request of model execution on main thread.
|
|
319
|
-
if (type === constants_1.THREAD_EVENT_TYPES.REQUEST_EXECUTE) {
|
|
320
|
-
try {
|
|
321
|
-
// Callback the resolver function
|
|
322
|
-
const result = yield this.externalResolverModule.sparkService(payload, context);
|
|
323
|
-
// Send back the response to worker thread
|
|
324
|
-
workerPool.postMessage({
|
|
325
|
-
payload: result,
|
|
326
|
-
type: constants_1.THREAD_EVENT_TYPES.RESPONSE,
|
|
327
|
-
threadId,
|
|
328
|
-
});
|
|
344
|
+
if (type === constants_1.THREAD_EVENT_TYPES.STATS_REPORT) {
|
|
345
|
+
const { payload, threadId } = requestData;
|
|
346
|
+
const worker = this.models.get(workerPool.id);
|
|
347
|
+
if (!worker) {
|
|
348
|
+
// Worker has been removed and this is a stale report
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// Initialize stats object if it doesn't exist
|
|
352
|
+
if (!worker.stats) {
|
|
353
|
+
worker.stats = {};
|
|
354
|
+
}
|
|
355
|
+
// Initialize stats for threadId if it doesn't exist
|
|
356
|
+
if (!worker.stats[threadId]) {
|
|
357
|
+
worker.stats[threadId] = {
|
|
358
|
+
init_memory: 0,
|
|
359
|
+
init_time_ms: 0,
|
|
360
|
+
ready_ts: Date.now(),
|
|
361
|
+
peak_memory: 0,
|
|
362
|
+
current_memory: 0,
|
|
363
|
+
last_execute_consume_memory: 0,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (worker && worker.stats && worker.stats[threadId]) {
|
|
367
|
+
worker.stats[threadId].current_memory = payload.memoryUsage;
|
|
368
|
+
if (payload.memoryUsage > worker.stats[threadId].peak_memory) {
|
|
369
|
+
worker.stats[threadId].peak_memory = payload.memoryUsage;
|
|
329
370
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
371
|
+
reportedStats += 1;
|
|
372
|
+
if (reportedStats >= workerSize) {
|
|
373
|
+
reportedStats = 0;
|
|
374
|
+
setTimeout(() => {
|
|
375
|
+
if (!this.isExist(workerPool.id)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.emit("worker_report_stats", {
|
|
379
|
+
id: workerPool.id,
|
|
380
|
+
thread_stats: worker.stats,
|
|
381
|
+
current_memory: Object.values(worker.stats)
|
|
382
|
+
.map((thread) => {
|
|
383
|
+
if (utils.isNumber(thread.current_memory)) {
|
|
384
|
+
return thread.current_memory;
|
|
385
|
+
}
|
|
386
|
+
return 0;
|
|
387
|
+
})
|
|
388
|
+
.reduce((sum, memory) => sum + memory, 0),
|
|
389
|
+
});
|
|
390
|
+
}, 10);
|
|
337
391
|
}
|
|
338
392
|
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const workerName = path_1.default.basename(modelPack.js, path_1.default.extname(modelPack.js)) || "";
|
|
396
|
+
workerPool = new workerPool_1.default(id, workerSize, workerPath, {
|
|
397
|
+
errorHandler: (e) => logger_1.default &&
|
|
398
|
+
logger_1.default.error({
|
|
399
|
+
EventType: "Worker.Error",
|
|
400
|
+
Error: e,
|
|
401
|
+
ModelId: id,
|
|
402
|
+
TextMessage: e.message,
|
|
403
|
+
}, "Worker Error"),
|
|
404
|
+
onlineHandler: () => logger_1.default &&
|
|
405
|
+
logger_1.default.info({ EventType: "Worker.Online", ModelId: id }, `Worker ${id} online`),
|
|
406
|
+
// Receive messages from worker here
|
|
407
|
+
messageHandler: processWorkerThreadRequest,
|
|
408
|
+
workerOptions: {
|
|
409
|
+
name: workerName,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
this.logger.debug({
|
|
413
|
+
TimeStamp: Date.now(),
|
|
414
|
+
EventType: "Runner.ModelInitialize",
|
|
415
|
+
ModelId: id,
|
|
416
|
+
});
|
|
417
|
+
return new Promise((resolve, reject) => {
|
|
418
|
+
let limit = 0;
|
|
419
|
+
const stats = {};
|
|
420
|
+
const checkModelStatus = async () => {
|
|
421
|
+
// Send isReady to all workers in parallel
|
|
422
|
+
const readyPromises = Array.from({ length: workerSize }, () => workerPool.execute("isReady"));
|
|
423
|
+
const results = await Promise.all(readyPromises);
|
|
424
|
+
// Collect stats from ready workers
|
|
425
|
+
for (const readyRes of results) {
|
|
426
|
+
if (readyRes && readyRes.threadId && !stats[readyRes.threadId]) {
|
|
365
427
|
stats[readyRes.threadId] = {
|
|
366
428
|
init_memory: readyRes.memoryUsage,
|
|
367
429
|
init_time_ms: readyRes.time,
|
|
368
|
-
ready_ts:
|
|
430
|
+
ready_ts: readyRes.payload?.readyTs || readyRes.readyTs,
|
|
369
431
|
peak_memory: readyRes.memoryUsage,
|
|
370
|
-
last_execute_consume_memory: readyRes.memoryUsage
|
|
432
|
+
last_execute_consume_memory: readyRes.memoryUsage,
|
|
371
433
|
};
|
|
372
|
-
|
|
373
|
-
logger_1.default.debug({
|
|
434
|
+
this.logger.debug({
|
|
374
435
|
TimeStamp: Date.now(),
|
|
375
436
|
EventType: "Runner.ModelInitialize.Completed",
|
|
376
437
|
ModelId: id,
|
|
438
|
+
ThreadId: readyRes.threadId,
|
|
377
439
|
});
|
|
378
|
-
if (readyCount === 0) {
|
|
379
|
-
resolve({
|
|
380
|
-
instance: workerPool,
|
|
381
|
-
compilerVersion,
|
|
382
|
-
compatibilityStatus,
|
|
383
|
-
size: workerSize,
|
|
384
|
-
stats: stats
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
setTimeout(checkModelStatus, 1);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
else if (limit >= 2000) {
|
|
392
|
-
// 1 second
|
|
393
|
-
reject(new Error("Model initialize issue"));
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
setTimeout(checkModelStatus, 15);
|
|
397
|
-
// eslint-disable-next-line no-plusplus
|
|
398
|
-
limit++;
|
|
399
440
|
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
441
|
+
}
|
|
442
|
+
const readyCount = Object.keys(stats).length;
|
|
443
|
+
if (readyCount >= workerSize) {
|
|
444
|
+
resolve({
|
|
445
|
+
instance: workerPool,
|
|
446
|
+
compilerVersion,
|
|
447
|
+
compatibilityStatus,
|
|
448
|
+
size: workerSize,
|
|
449
|
+
stats: stats,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
else if (limit >= 2000) {
|
|
453
|
+
reject(new Error("Model initialize issue"));
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
limit++;
|
|
457
|
+
setTimeout(checkModelStatus, 15);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
setTimeout(checkModelStatus, 15);
|
|
403
461
|
});
|
|
404
462
|
}
|
|
405
463
|
/* istanbul ignore next */
|
|
406
|
-
_initializeModelInstance(m) {
|
|
407
|
-
|
|
464
|
+
async _initializeModelInstance(m) {
|
|
465
|
+
const tempFolder = path_1.default.join(this._tempWorkerFolder.name, `${m.id}-${Date.now()}`);
|
|
466
|
+
try {
|
|
467
|
+
await fs_1.default.promises.mkdir(tempFolder, { mode: 0o750 });
|
|
468
|
+
}
|
|
469
|
+
catch (_err) {
|
|
470
|
+
this.logger.info(`${tempFolder} existed`);
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
408
473
|
if (utils.isHttpURL(m.url)) {
|
|
409
|
-
const { instance, compilerVersion, compatibilityStatus, stats } =
|
|
474
|
+
const { instance, compilerVersion, compatibilityStatus, stats } = await this._createModelInstanceFromURL(m.id, m.url, tempFolder, this._tempModelFolder.name, m.size);
|
|
410
475
|
m.ready = true;
|
|
411
476
|
m.worker = instance;
|
|
412
477
|
m.compatibility_status = compatibilityStatus;
|
|
413
478
|
m.compilerVersion = compilerVersion;
|
|
414
479
|
m.stats = stats;
|
|
415
480
|
m.responseTimeMetrics = new responseTimeMetric_1.default();
|
|
481
|
+
m.workerFolder = tempFolder;
|
|
416
482
|
return Promise.resolve();
|
|
417
483
|
}
|
|
418
|
-
if (
|
|
419
|
-
const { instance, compilerVersion, compatibilityStatus, stats } =
|
|
484
|
+
if (await utils.isPath(m.url)) {
|
|
485
|
+
const { instance, compilerVersion, compatibilityStatus, stats } = await this._createModelInstance(m.id, m.url, tempFolder, m.size);
|
|
420
486
|
m.ready = true;
|
|
421
487
|
m.worker = instance;
|
|
422
488
|
m.compatibility_status = compatibilityStatus;
|
|
423
489
|
m.compilerVersion = compilerVersion;
|
|
424
490
|
m.stats = stats;
|
|
425
491
|
m.responseTimeMetrics = new responseTimeMetric_1.default();
|
|
492
|
+
m.workerFolder = tempFolder;
|
|
426
493
|
return Promise.resolve();
|
|
427
494
|
}
|
|
428
495
|
if (Buffer.isBuffer(m.url)) {
|
|
429
496
|
const model = stream_1.Readable.from(m.url);
|
|
430
497
|
const modelPath = path_1.default.join(this._tempWorkerFolder.name, `${m.id}.zip`);
|
|
431
498
|
const fileWriterStream = (0, fs_1.createWriteStream)(modelPath);
|
|
432
|
-
|
|
433
|
-
const { instance, compilerVersion, compatibilityStatus, stats } =
|
|
499
|
+
await pipeline(model, fileWriterStream);
|
|
500
|
+
const { instance, compilerVersion, compatibilityStatus, stats } = await this._createModelInstance(m.id, modelPath, tempFolder, m.size);
|
|
434
501
|
m.ready = true;
|
|
435
502
|
m.worker = instance;
|
|
436
503
|
m.compatibility_status = compatibilityStatus;
|
|
437
504
|
m.compilerVersion = compilerVersion;
|
|
438
505
|
m.stats = stats;
|
|
439
506
|
m.responseTimeMetrics = new responseTimeMetric_1.default();
|
|
507
|
+
m.workerFolder = tempFolder;
|
|
440
508
|
return Promise.resolve();
|
|
441
509
|
}
|
|
442
|
-
}
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
await fs_1.default.promises.rm(tempFolder, { recursive: true, force: true });
|
|
513
|
+
throw err;
|
|
514
|
+
}
|
|
443
515
|
}
|
|
444
516
|
/* istanbul ignore next */
|
|
445
|
-
_createModelInstanceFromURL(id, url, workerFolder, modelFolder, size) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
});
|
|
517
|
+
async _createModelInstanceFromURL(id, url, workerFolder, modelFolder, size) {
|
|
518
|
+
try {
|
|
519
|
+
const modelPath = path_1.default.join(modelFolder, `${id}.zip`);
|
|
520
|
+
const downloadStream = got_1.default.stream(url);
|
|
521
|
+
const fileWriterStream = (0, fs_1.createWriteStream)(modelPath);
|
|
522
|
+
await pipeline(downloadStream, fileWriterStream);
|
|
523
|
+
const instance = await this._createModelInstance(id, modelPath, workerFolder, size);
|
|
524
|
+
return instance;
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
throw new error_1.default.ModelError(`Error createModelInstanceFromURL: id:${id}, url:${url}, error:${err.message}`);
|
|
528
|
+
}
|
|
459
529
|
}
|
|
460
530
|
/**
|
|
461
531
|
* Append and initialize model in preparation for the `execute()`.
|
|
@@ -465,49 +535,42 @@ class WasmRunner {
|
|
|
465
535
|
*
|
|
466
536
|
* @returns {Promise<void>}
|
|
467
537
|
*/
|
|
468
|
-
append(modelConfig) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
538
|
+
async append(modelConfig) {
|
|
539
|
+
if (this.options.compatibilityCheckEnabled) {
|
|
540
|
+
this.safeCompiler = await this.getSafeCompilerVersion(this.options.runnerVersion);
|
|
541
|
+
}
|
|
542
|
+
if (!this.isExist(modelConfig.id)) {
|
|
543
|
+
const model = {
|
|
544
|
+
id: modelConfig.id,
|
|
545
|
+
url: modelConfig.url,
|
|
546
|
+
size: modelConfig.size,
|
|
547
|
+
busy: 0,
|
|
548
|
+
};
|
|
473
549
|
if (!this.isExist(modelConfig.id)) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
};
|
|
480
|
-
if (!this.isExist(modelConfig.id)) {
|
|
481
|
-
if (this.initializingModels.findIndex((im) => im === modelConfig.id) ===
|
|
482
|
-
-1) {
|
|
483
|
-
try {
|
|
484
|
-
this.initializingModels.push(modelConfig.id);
|
|
485
|
-
yield this._initializeModelInstance(model);
|
|
486
|
-
this.models.push(model);
|
|
487
|
-
}
|
|
488
|
-
catch (e) {
|
|
489
|
-
throw e;
|
|
490
|
-
}
|
|
491
|
-
finally {
|
|
492
|
-
this.initializingModels = this.initializingModels.filter((im) => im !== modelConfig.id);
|
|
493
|
-
}
|
|
550
|
+
if (!this.initializingModels.has(modelConfig.id)) {
|
|
551
|
+
try {
|
|
552
|
+
this.initializingModels.add(modelConfig.id);
|
|
553
|
+
await this._initializeModelInstance(model);
|
|
554
|
+
this.models.set(model.id, model);
|
|
494
555
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
556
|
+
finally {
|
|
557
|
+
this.initializingModels.delete(modelConfig.id);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
const maxRetry = 600;
|
|
562
|
+
let retry = 0;
|
|
563
|
+
while (this.initializingModels.has(modelConfig.id) &&
|
|
564
|
+
retry < maxRetry) {
|
|
565
|
+
await delay(100);
|
|
566
|
+
retry++;
|
|
567
|
+
}
|
|
568
|
+
if (retry >= maxRetry || !this.isExist(modelConfig.id)) {
|
|
569
|
+
throw new error_1.default.ModelInitializationError(modelConfig.id);
|
|
507
570
|
}
|
|
508
571
|
}
|
|
509
572
|
}
|
|
510
|
-
}
|
|
573
|
+
}
|
|
511
574
|
}
|
|
512
575
|
/**
|
|
513
576
|
* Remove model from the list of initialized model useful for GC.
|
|
@@ -517,21 +580,34 @@ class WasmRunner {
|
|
|
517
580
|
*
|
|
518
581
|
* @returns {Promise<void>}
|
|
519
582
|
*/
|
|
520
|
-
remove(id) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
583
|
+
async remove(id) {
|
|
584
|
+
const model = this.models.get(id);
|
|
585
|
+
if (model) {
|
|
586
|
+
this.models.delete(id);
|
|
587
|
+
setImmediate(async () => {
|
|
588
|
+
await model.worker.execute("destroy");
|
|
525
589
|
model.worker.destroy();
|
|
526
590
|
delete model.worker;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
591
|
+
try {
|
|
592
|
+
await fs_1.default.promises.rm(model.workerFolder, {
|
|
593
|
+
recursive: true,
|
|
594
|
+
force: true,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
this.logger.warn({
|
|
599
|
+
TimeStamp: Date.now(),
|
|
600
|
+
EventType: "Runner.Remove.ModelError",
|
|
601
|
+
TextMessage: `Failed to remove model ${model.id} worker folder`,
|
|
602
|
+
JSONPayload: { error: err.message, modelId: model.id },
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
530
607
|
}
|
|
531
608
|
/* istanbul ignore next */
|
|
532
609
|
_parseError(rawResponse) {
|
|
533
|
-
|
|
534
|
-
let errors = (_a = rawResponse.response_data) === null || _a === void 0 ? void 0 : _a.errors.reduce((prev, curr) => {
|
|
610
|
+
let errors = rawResponse.response_data?.errors.reduce((prev, curr) => {
|
|
535
611
|
const { source_path } = curr;
|
|
536
612
|
if (!source_path) {
|
|
537
613
|
return prev;
|
|
@@ -575,20 +651,23 @@ class WasmRunner {
|
|
|
575
651
|
return errors;
|
|
576
652
|
}
|
|
577
653
|
_getModel(id) {
|
|
578
|
-
const model = this.models.
|
|
654
|
+
const model = this.models.get(id);
|
|
579
655
|
if (!model)
|
|
580
656
|
throw new error_1.default.MissingModelError(id);
|
|
581
657
|
if (!model.ready)
|
|
582
658
|
throw new error_1.default.ModelInitializationError(id);
|
|
583
659
|
return model;
|
|
584
660
|
}
|
|
661
|
+
getModel(id) {
|
|
662
|
+
return this._getModel(id);
|
|
663
|
+
}
|
|
585
664
|
getModelSize(id) {
|
|
586
665
|
const model = this._getModel(id);
|
|
587
666
|
return model.size || 1;
|
|
588
667
|
}
|
|
589
668
|
getModelCompilerVersion(id) {
|
|
590
669
|
const model = this._getModel(id);
|
|
591
|
-
if (model.compilerVersion && model.compilerVersion !==
|
|
670
|
+
if (model.compilerVersion && model.compilerVersion !== "undefined") {
|
|
592
671
|
return model.compilerVersion;
|
|
593
672
|
}
|
|
594
673
|
return undefined;
|
|
@@ -602,7 +681,7 @@ class WasmRunner {
|
|
|
602
681
|
try {
|
|
603
682
|
return !!this._getModel(id);
|
|
604
683
|
}
|
|
605
|
-
catch (
|
|
684
|
+
catch (_err) {
|
|
606
685
|
return false;
|
|
607
686
|
}
|
|
608
687
|
}
|
|
@@ -614,7 +693,7 @@ class WasmRunner {
|
|
|
614
693
|
case types_1.Compatibility.INCOMPATIBLE:
|
|
615
694
|
throw new error_1.default.ModelExecuteError(this.getIncompatibleMessage(model), model.id, input);
|
|
616
695
|
case types_1.Compatibility.PARTIAL:
|
|
617
|
-
|
|
696
|
+
this.logger.warn({
|
|
618
697
|
TimeStamp: Date.now(),
|
|
619
698
|
EventType: "Runner.Execute.Compatibility.Warning",
|
|
620
699
|
TextMessage: this.getPartiallyCompatibleMessage(model),
|
|
@@ -623,30 +702,42 @@ class WasmRunner {
|
|
|
623
702
|
return true;
|
|
624
703
|
case types_1.Compatibility.COMPATIBLE:
|
|
625
704
|
return true;
|
|
705
|
+
default:
|
|
706
|
+
return true;
|
|
626
707
|
}
|
|
627
|
-
return true;
|
|
628
708
|
}
|
|
629
709
|
getModelsStats() {
|
|
630
|
-
return this.models.map(model => {
|
|
631
|
-
const stats =
|
|
632
|
-
Object.keys(stats).forEach(threadId => {
|
|
633
|
-
stats[threadId] =
|
|
634
|
-
Object.keys(stats[threadId]).forEach(k => {
|
|
635
|
-
if (k.indexOf(
|
|
710
|
+
return Array.from(this.models.values()).map((model) => {
|
|
711
|
+
const stats = { ...model.stats };
|
|
712
|
+
Object.keys(stats).forEach((threadId) => {
|
|
713
|
+
stats[threadId] = { ...stats[threadId] };
|
|
714
|
+
Object.keys(stats[threadId]).forEach((k) => {
|
|
715
|
+
if (k.indexOf("_memory") > -1) {
|
|
636
716
|
stats[threadId][`${k}_mb`] = utils.formatMemory(stats[threadId][k]);
|
|
637
717
|
delete stats[threadId][k];
|
|
638
718
|
}
|
|
639
|
-
if (k.indexOf(
|
|
640
|
-
stats[threadId].uptime_ms = Date.now() - stats[threadId]
|
|
719
|
+
if (k.indexOf("ready_ts") > -1) {
|
|
720
|
+
stats[threadId].uptime_ms = Date.now() - stats[threadId].ready_ts;
|
|
641
721
|
delete stats[threadId][k];
|
|
642
722
|
}
|
|
643
723
|
});
|
|
644
724
|
});
|
|
645
725
|
return {
|
|
646
|
-
|
|
726
|
+
thread_stats: {
|
|
727
|
+
...stats,
|
|
728
|
+
},
|
|
729
|
+
memory_usage_mb: Object.values(stats)
|
|
730
|
+
.map((thread) => thread.current_memory_mb || 0)
|
|
731
|
+
.reduce((sum, memory) => sum + memory, 0),
|
|
732
|
+
uptime_ms: Math.min(...Object.keys(stats).map((k) => stats[k].uptime_ms)),
|
|
733
|
+
min_time_ms: model.responseTimeMetrics.min,
|
|
734
|
+
mean_time_ms: model.responseTimeMetrics.mean,
|
|
735
|
+
p95_time_ms: model.responseTimeMetrics.p95,
|
|
736
|
+
p99_time_ms: model.responseTimeMetrics.p99,
|
|
737
|
+
max_time_ms: model.responseTimeMetrics.max,
|
|
647
738
|
busy: model.busy,
|
|
648
739
|
size: model.size,
|
|
649
|
-
id: model.id
|
|
740
|
+
id: model.id,
|
|
650
741
|
};
|
|
651
742
|
});
|
|
652
743
|
}
|
|
@@ -662,163 +753,231 @@ class WasmRunner {
|
|
|
662
753
|
* @param {InputObject} input - Input data for calculation
|
|
663
754
|
* @returns {Promise<ResponseObject>} - Response model data
|
|
664
755
|
*/
|
|
665
|
-
execute(
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
756
|
+
async execute(input, id = undefined, raw = false, cancelToken) {
|
|
757
|
+
let logInput;
|
|
758
|
+
const token = cancelToken ? cancelToken.getToken() : undefined;
|
|
759
|
+
if (utils.isV3Input(input)) {
|
|
760
|
+
logInput = {
|
|
761
|
+
request_data: {
|
|
762
|
+
inputs: input?.request_data?.inputs,
|
|
763
|
+
},
|
|
764
|
+
request_meta: {
|
|
765
|
+
...input.request_meta,
|
|
766
|
+
...(input.request_meta._ctx ? { _ctx: "[REDACTED]" } : {}),
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
this.logger.trace({
|
|
770
|
+
TimeStamp: Date.now(),
|
|
771
|
+
EventType: "Runner.Execute.V3",
|
|
772
|
+
TextMessage: `EXECUTING`,
|
|
773
|
+
JSONPayload: { input: logInput, modelId: id },
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
this.logger.trace({
|
|
685
778
|
TimeStamp: Date.now(),
|
|
686
779
|
EventType: "Runner.Execute",
|
|
687
|
-
|
|
688
|
-
|
|
780
|
+
TextMessage: `EXECUTING`,
|
|
781
|
+
JSONPayload: { input: input, modelId: id },
|
|
689
782
|
});
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
input.request_data
|
|
783
|
+
}
|
|
784
|
+
const callid = input.callid || (0, cuid2_1.createId)();
|
|
785
|
+
const version_uuid = id ||
|
|
786
|
+
input?.request_meta?.version_uuid ||
|
|
787
|
+
input?.request_meta?.version_id ||
|
|
788
|
+
input?.version_id;
|
|
789
|
+
if (input) {
|
|
790
|
+
if (raw) {
|
|
791
|
+
if (!input.request_data) {
|
|
792
|
+
input.request_data = {};
|
|
700
793
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
model.busy = model.busy + 1;
|
|
794
|
+
input.request_data._raw = true;
|
|
795
|
+
}
|
|
796
|
+
const model = this._getModel(version_uuid);
|
|
797
|
+
try {
|
|
798
|
+
if (!input.callid) {
|
|
799
|
+
input.callid = callid;
|
|
800
|
+
}
|
|
801
|
+
this.logger.debug({
|
|
802
|
+
TimeStamp: Date.now(),
|
|
803
|
+
EventType: "Runner.Execute",
|
|
804
|
+
TextMessage: `PREEXECUTING ${id}`,
|
|
805
|
+
JSONPayload: {
|
|
806
|
+
runnerCallId: callid,
|
|
807
|
+
ModelId: id,
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
if (this.isModelCompatible(model, input)) {
|
|
811
|
+
const start = Date.now();
|
|
812
|
+
if (token) {
|
|
813
|
+
token.throwIfCancelled(version_uuid, input);
|
|
723
814
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (res.memoryUsage > model.stats[res.threadId].peak_memory) {
|
|
730
|
-
model.stats[res.threadId].peak_memory = res.memoryUsage;
|
|
731
|
-
}
|
|
732
|
-
model.stats[res.threadId].last_execute_consume_memory = res.memoryUsage;
|
|
733
|
-
model.responseTimeMetrics.record(execTime);
|
|
734
|
-
if (res.error) {
|
|
735
|
-
throw res.error;
|
|
736
|
-
}
|
|
737
|
-
parsedResult = res.payload;
|
|
738
|
-
logger_1.default.debug({
|
|
739
|
-
TimeStamp: Date.now(),
|
|
740
|
-
EventType: "Runner.Execute.Completed",
|
|
741
|
-
executeDuration: Date.now() - start,
|
|
815
|
+
this.logger.debug({
|
|
816
|
+
TimeStamp: Date.now(),
|
|
817
|
+
EventType: "Runner.Execute",
|
|
818
|
+
TextMessage: `START EXECUTING ${id}`,
|
|
819
|
+
JSONPayload: {
|
|
742
820
|
runnerCallId: callid,
|
|
743
821
|
ModelId: id,
|
|
744
|
-
}
|
|
745
|
-
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
const res = await model.worker.execute(input);
|
|
825
|
+
const execTime = Date.now() - start;
|
|
826
|
+
this.logger.debug({
|
|
827
|
+
TimeStamp: Date.now(),
|
|
828
|
+
EventType: "Runner.Execute.Completed",
|
|
829
|
+
TextMessage: `EXECUTE COMPLETED ${id}`,
|
|
830
|
+
JSONPayload: {
|
|
831
|
+
executeDuration: execTime,
|
|
832
|
+
runnerCallId: callid,
|
|
833
|
+
ModelId: id,
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
if (res.memoryUsage > model.stats[res.threadId].peak_memory) {
|
|
837
|
+
model.stats[res.threadId].peak_memory = res.memoryUsage;
|
|
746
838
|
}
|
|
747
|
-
|
|
748
|
-
|
|
839
|
+
model.stats[res.threadId].last_execute_consume_memory =
|
|
840
|
+
res.memoryUsage;
|
|
841
|
+
model.responseTimeMetrics.record(execTime);
|
|
842
|
+
if (res.error) {
|
|
843
|
+
throw res.error;
|
|
749
844
|
}
|
|
845
|
+
return res.payload;
|
|
750
846
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
throw err;
|
|
758
|
-
}
|
|
759
|
-
throw new error_1.default.ModelExecuteError(err.message, version_uuid, input);
|
|
847
|
+
throw new error_1.default.ModelExecuteError(this.getIncompatibleMessage(model), version_uuid, input);
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
this.logger.error(err);
|
|
851
|
+
if (err.name === "validation_error") {
|
|
852
|
+
throw err;
|
|
760
853
|
}
|
|
761
|
-
|
|
762
|
-
|
|
854
|
+
if (err.name === "execution_cancelled") {
|
|
855
|
+
throw err;
|
|
763
856
|
}
|
|
857
|
+
throw new error_1.default.ModelExecuteError(err.message, version_uuid, input);
|
|
764
858
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
throw new error_1.default.ParameterRequiredError("request_meta.version_uuid", input);
|
|
862
|
+
}
|
|
769
863
|
}
|
|
770
|
-
getNeuronCompilerVersion(logFile) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
864
|
+
async getNeuronCompilerVersion(logFile) {
|
|
865
|
+
let data;
|
|
866
|
+
let modelJsonInfo;
|
|
867
|
+
try {
|
|
868
|
+
data = fs_1.default.readFileSync(logFile.replace(/"/g, ""), "utf8");
|
|
869
|
+
modelJsonInfo = JSON.parse(data);
|
|
870
|
+
}
|
|
871
|
+
catch (err) {
|
|
872
|
+
this.logger.warn({
|
|
873
|
+
TimeStamp: Date.now(),
|
|
874
|
+
EventType: "Runner.GetNeuronCompilerVersion",
|
|
875
|
+
TextMessage: `Failed to get neuron compiler version from ${logFile}`,
|
|
876
|
+
JSONPayload: {
|
|
877
|
+
error: err.message,
|
|
878
|
+
logFile,
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
if (!semver_1.default.valid(modelJsonInfo.CompilerVersion)) {
|
|
884
|
+
throw new error_1.default.CompatibilityJsonError(`The Wasm was compiled using ${modelJsonInfo.CompilerVersion} which is not supported by the compatibility check.`);
|
|
885
|
+
}
|
|
886
|
+
const compilerVersion = semver_1.default.coerce(modelJsonInfo.CompilerVersion).version;
|
|
887
|
+
return compilerVersion;
|
|
781
888
|
}
|
|
782
|
-
checkCompilerVersionCompatibility(modelCompilerVersion) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (modelCompilerVersion) {
|
|
787
|
-
if (semver_1.default.lte(modelCompilerVersion, safeCompilerVersion)) {
|
|
788
|
-
return types_1.Compatibility.COMPATIBLE;
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
return types_1.Compatibility.PARTIAL;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
889
|
+
async checkCompilerVersionCompatibility(modelCompilerVersion) {
|
|
890
|
+
try {
|
|
891
|
+
const safeCompilerVersion = this.safeCompiler;
|
|
892
|
+
if (modelCompilerVersion) {
|
|
893
|
+
if (semver_1.default.lte(modelCompilerVersion, safeCompilerVersion)) {
|
|
795
894
|
return types_1.Compatibility.COMPATIBLE;
|
|
796
895
|
}
|
|
896
|
+
return types_1.Compatibility.PARTIAL;
|
|
797
897
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
return types_1.Compatibility.INCOMPATIBLE;
|
|
804
|
-
}
|
|
805
|
-
else {
|
|
806
|
-
throw err;
|
|
807
|
-
}
|
|
898
|
+
return types_1.Compatibility.COMPATIBLE;
|
|
899
|
+
}
|
|
900
|
+
catch (err) {
|
|
901
|
+
if (err.code === "ENOENT" || err.message === "File not found") {
|
|
902
|
+
return types_1.Compatibility.COMPATIBLE;
|
|
808
903
|
}
|
|
809
|
-
|
|
904
|
+
if (err instanceof SyntaxError) {
|
|
905
|
+
return types_1.Compatibility.INCOMPATIBLE;
|
|
906
|
+
}
|
|
907
|
+
throw err;
|
|
908
|
+
}
|
|
810
909
|
}
|
|
811
910
|
getSafeCompilerVersion(runnerVersion) {
|
|
812
|
-
|
|
813
|
-
const runnerVersions = (_a = this.options.compatibilityList) === null || _a === void 0 ? void 0 : _a.runner_versions;
|
|
911
|
+
const runnerVersions = this.options.compatibilityList?.runner_versions;
|
|
814
912
|
if (!this.options.compatibilityList) {
|
|
815
913
|
throw new error_1.default.CompatibilityJsonError("No runner compiler compatibility list provided");
|
|
816
914
|
}
|
|
817
|
-
if (!
|
|
915
|
+
if (!Object.prototype.hasOwnProperty.call(runnerVersions, runnerVersion)) {
|
|
818
916
|
throw new error_1.default.CompatibilityJsonError(`No compiler compatibility found for the runner version ${runnerVersion}`);
|
|
819
917
|
}
|
|
820
918
|
return runnerVersions[runnerVersion].safe_compiler;
|
|
821
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Dispose all resources: terminate all running models and clean up temp files.
|
|
922
|
+
*
|
|
923
|
+
* @async
|
|
924
|
+
* @returns {Promise<void>}
|
|
925
|
+
*/
|
|
926
|
+
async dispose() {
|
|
927
|
+
this.logger.debug({
|
|
928
|
+
TimeStamp: Date.now(),
|
|
929
|
+
EventType: "Runner.Dispose",
|
|
930
|
+
TextMessage: "Disposing all resources",
|
|
931
|
+
});
|
|
932
|
+
// Terminate all running models
|
|
933
|
+
const modelIds = Array.from(this.models.keys());
|
|
934
|
+
await Promise.all(modelIds.map((id) => this.remove(id)));
|
|
935
|
+
this.initializingModels.clear();
|
|
936
|
+
// Clean up temp folders
|
|
937
|
+
try {
|
|
938
|
+
if (this._tempWorkerFolder?.name) {
|
|
939
|
+
await fs_1.default.promises.rm(this._tempWorkerFolder.name, {
|
|
940
|
+
recursive: true,
|
|
941
|
+
force: true,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
946
|
+
this.logger.warn({
|
|
947
|
+
TimeStamp: Date.now(),
|
|
948
|
+
EventType: "Runner.Dispose.TempFolderError",
|
|
949
|
+
TextMessage: "Failed to remove temp worker folder",
|
|
950
|
+
JSONPayload: {
|
|
951
|
+
error: err.message,
|
|
952
|
+
folder: this._tempWorkerFolder?.name,
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
if (this._tempModelFolder?.name) {
|
|
958
|
+
await fs_1.default.promises.rm(this._tempModelFolder.name, {
|
|
959
|
+
recursive: true,
|
|
960
|
+
force: true,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
catch (err) {
|
|
965
|
+
this.logger.warn({
|
|
966
|
+
TimeStamp: Date.now(),
|
|
967
|
+
EventType: "Runner.Dispose.TempFolderError",
|
|
968
|
+
TextMessage: "Failed to remove temp model folder",
|
|
969
|
+
JSONPayload: {
|
|
970
|
+
error: err.message,
|
|
971
|
+
folder: this._tempModelFolder?.name,
|
|
972
|
+
},
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
this.logger.debug({
|
|
976
|
+
TimeStamp: Date.now(),
|
|
977
|
+
EventType: "Runner.Dispose.Completed",
|
|
978
|
+
TextMessage: "All resources disposed",
|
|
979
|
+
});
|
|
980
|
+
}
|
|
822
981
|
}
|
|
823
982
|
exports.WasmRunner = WasmRunner;
|
|
824
983
|
//# sourceMappingURL=node.js.map
|