@rosepetal/node-red-contrib-async-function 1.0.2 → 1.0.3
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 +8 -6
- package/nodes/async-function.html +9 -7
- package/nodes/async-function.js +320 -27
- package/nodes/lib/child-process-pool.js +33 -6
- package/nodes/lib/child-process-script.js +179 -5
- package/nodes/lib/message-serializer.js +171 -16
- package/nodes/lib/module-installer.js +48 -25
- package/nodes/lib/shared-memory-manager.js +50 -12
- package/nodes/lib/worker-pool.js +47 -10
- package/nodes/lib/worker-script.js +206 -10
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const fs = require('fs').promises;
|
|
10
|
-
const
|
|
10
|
+
const { constants: fsConstants } = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const os = require('os');
|
|
13
13
|
const crypto = require('crypto');
|
|
@@ -22,7 +22,7 @@ class SharedMemoryManager {
|
|
|
22
22
|
*/
|
|
23
23
|
constructor(options = {}) {
|
|
24
24
|
this.threshold = options.threshold ?? 100 * 1024; // 100KB default
|
|
25
|
-
this.shmPath =
|
|
25
|
+
this.shmPath = options.shmPath || null;
|
|
26
26
|
this.trackAttachments = options.trackAttachments !== false;
|
|
27
27
|
this.taskAttachments = new Map(); // taskId → Set<filePath>
|
|
28
28
|
this.globalAttachments = new Set(); // All active files
|
|
@@ -32,24 +32,26 @@ class SharedMemoryManager {
|
|
|
32
32
|
filesCreated: 0,
|
|
33
33
|
filesDeleted: 0
|
|
34
34
|
};
|
|
35
|
+
this.initialized = false;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.
|
|
39
|
-
|
|
37
|
+
this.ready = this.initialize(options).catch((err) => {
|
|
38
|
+
this.shmPath = os.tmpdir();
|
|
39
|
+
this.initialized = true;
|
|
40
|
+
console.warn(`Failed to initialize shared memory manager: ${err.message}`);
|
|
41
|
+
});
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Detect platform-specific shared memory path
|
|
44
46
|
* @returns {string} Path to shared memory directory
|
|
45
47
|
*/
|
|
46
|
-
detectShmPath() {
|
|
48
|
+
async detectShmPath() {
|
|
47
49
|
// Linux: /dev/shm (RAM-backed tmpfs)
|
|
48
50
|
const shmPath = '/dev/shm';
|
|
49
51
|
|
|
50
52
|
try {
|
|
51
53
|
// Check if /dev/shm exists and is writable
|
|
52
|
-
|
|
54
|
+
await fs.access(shmPath, fsConstants.W_OK);
|
|
53
55
|
return shmPath;
|
|
54
56
|
} catch (err) {
|
|
55
57
|
// Fall back to os.tmpdir() (macOS, Windows, or Linux without /dev/shm)
|
|
@@ -57,6 +59,35 @@ class SharedMemoryManager {
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Initialize shared memory manager
|
|
64
|
+
* @param {object} options - Configuration options
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
async initialize(options = {}) {
|
|
68
|
+
if (this.initialized) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this.shmPath) {
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(this.shmPath, fsConstants.W_OK);
|
|
75
|
+
} catch (_err) {
|
|
76
|
+
this.shmPath = await this.detectShmPath();
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
this.shmPath = await this.detectShmPath();
|
|
80
|
+
}
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
|
|
83
|
+
// Cleanup orphaned files from previous crashes (async, non-blocking)
|
|
84
|
+
if (options.cleanupOrphanedFiles !== false) {
|
|
85
|
+
this.cleanupOrphanedFiles().catch((err) => {
|
|
86
|
+
console.warn(`Failed to cleanup orphaned files: ${err.message}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
/**
|
|
61
92
|
* Generate unique collision-resistant filename
|
|
62
93
|
* @param {number|string} taskId - Task identifier
|
|
@@ -80,6 +111,7 @@ class SharedMemoryManager {
|
|
|
80
111
|
* @returns {Promise<object>} Descriptor object
|
|
81
112
|
*/
|
|
82
113
|
async writeBuffer(buffer, taskId, bufferIndex) {
|
|
114
|
+
await this.ready;
|
|
83
115
|
// Check if buffer exceeds threshold
|
|
84
116
|
if (this.threshold > 0 && buffer.length <= this.threshold) {
|
|
85
117
|
// Return inline buffer (no shared memory needed)
|
|
@@ -127,6 +159,7 @@ class SharedMemoryManager {
|
|
|
127
159
|
* @returns {Promise<Buffer>} Buffer contents
|
|
128
160
|
*/
|
|
129
161
|
async readBuffer(descriptor, options = {}) {
|
|
162
|
+
await this.ready;
|
|
130
163
|
// Validate descriptor
|
|
131
164
|
if (!descriptor || typeof descriptor !== 'object') {
|
|
132
165
|
throw new Error('Invalid descriptor: must be an object');
|
|
@@ -234,6 +267,7 @@ class SharedMemoryManager {
|
|
|
234
267
|
* @returns {Promise<void>}
|
|
235
268
|
*/
|
|
236
269
|
async cleanupAll() {
|
|
270
|
+
await this.ready;
|
|
237
271
|
// Delete all tracked files
|
|
238
272
|
const deletePromises = [];
|
|
239
273
|
for (const filePath of this.globalAttachments) {
|
|
@@ -259,11 +293,11 @@ class SharedMemoryManager {
|
|
|
259
293
|
/**
|
|
260
294
|
* Cleanup orphaned files from previous crashes
|
|
261
295
|
*/
|
|
262
|
-
cleanupOrphanedFiles() {
|
|
296
|
+
async cleanupOrphanedFiles() {
|
|
263
297
|
const oneHourAgo = Date.now() - (60 * 60 * 1000);
|
|
264
298
|
|
|
265
299
|
try {
|
|
266
|
-
const files =
|
|
300
|
+
const files = await fs.readdir(this.shmPath);
|
|
267
301
|
|
|
268
302
|
for (const file of files) {
|
|
269
303
|
// Skip files that don't match our pattern
|
|
@@ -274,11 +308,15 @@ class SharedMemoryManager {
|
|
|
274
308
|
const filePath = path.join(this.shmPath, file);
|
|
275
309
|
|
|
276
310
|
try {
|
|
277
|
-
const stats =
|
|
311
|
+
const stats = await fs.stat(filePath);
|
|
278
312
|
|
|
279
313
|
// Only cleanup files older than 1 hour
|
|
280
314
|
if (stats.mtimeMs < oneHourAgo) {
|
|
281
|
-
|
|
315
|
+
await fs.unlink(filePath).catch((err) => {
|
|
316
|
+
if (err.code !== 'ENOENT') {
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
282
320
|
console.log(`Cleaned up orphaned file: ${file}`);
|
|
283
321
|
}
|
|
284
322
|
} catch (err) {
|
package/nodes/lib/worker-pool.js
CHANGED
|
@@ -17,6 +17,7 @@ const DEFAULT_CONFIG = {
|
|
|
17
17
|
taskTimeout: 30000, // Default task timeout: 30s
|
|
18
18
|
maxQueueSize: 100, // Max queued messages
|
|
19
19
|
shmThreshold: 0, // Always use shared memory for Buffers
|
|
20
|
+
transferMode: 'transfer', // transfer | shared | copy
|
|
20
21
|
libs: [], // External modules to load in workers
|
|
21
22
|
nodeRedUserDir: null, // Node-RED user directory for module resolution
|
|
22
23
|
workerScript: path.join(__dirname, 'worker-script.js')
|
|
@@ -79,6 +80,7 @@ class WorkerPool {
|
|
|
79
80
|
const worker = new Worker(this.config.workerScript, {
|
|
80
81
|
workerData: {
|
|
81
82
|
shmThreshold: this.config.shmThreshold,
|
|
83
|
+
transferMode: this.config.transferMode,
|
|
82
84
|
libs: this.config.libs || [],
|
|
83
85
|
nodeRedUserDir: this.config.nodeRedUserDir
|
|
84
86
|
}
|
|
@@ -262,19 +264,31 @@ class WorkerPool {
|
|
|
262
264
|
workerState.state = WorkerState.BUSY;
|
|
263
265
|
workerState.taskId = taskId;
|
|
264
266
|
|
|
265
|
-
this.
|
|
267
|
+
const transferList = this.config.transferMode === 'transfer' ? [] : null;
|
|
268
|
+
const transferSet = transferList ? new Set() : null;
|
|
269
|
+
|
|
270
|
+
this.serializer.sanitizeMessage(msg, null, taskId, {
|
|
271
|
+
transferMode: this.config.transferMode,
|
|
272
|
+
transferList,
|
|
273
|
+
transferSet
|
|
274
|
+
}).then(sanitizedMsg => {
|
|
266
275
|
// Start timeout after message preparation (matches hot-mode behavior)
|
|
267
276
|
this.timeoutManager.startTimeout(taskId, timeout, () => {
|
|
268
277
|
this.handleTimeout(workerState, taskId);
|
|
269
278
|
});
|
|
270
279
|
|
|
271
280
|
// Send task to worker
|
|
272
|
-
|
|
281
|
+
const payload = {
|
|
273
282
|
type: 'execute',
|
|
274
283
|
taskId,
|
|
275
284
|
code,
|
|
276
285
|
msg: sanitizedMsg
|
|
277
|
-
}
|
|
286
|
+
};
|
|
287
|
+
if (transferList && transferList.length > 0) {
|
|
288
|
+
workerState.worker.postMessage(payload, transferList);
|
|
289
|
+
} else {
|
|
290
|
+
workerState.worker.postMessage(payload);
|
|
291
|
+
}
|
|
278
292
|
}).catch(err => {
|
|
279
293
|
// Fail task if message prep fails
|
|
280
294
|
const callback = this.callbacks.get(taskId);
|
|
@@ -292,7 +306,7 @@ class WorkerPool {
|
|
|
292
306
|
* @param {object} message - Message from worker
|
|
293
307
|
*/
|
|
294
308
|
handleWorkerMessage(workerState, message) {
|
|
295
|
-
const { type, taskId, result, error, performance } = message;
|
|
309
|
+
const { type, taskId, result, error, performance, contextUpdates, logs } = message || {};
|
|
296
310
|
|
|
297
311
|
if (type === 'result') {
|
|
298
312
|
// Task completed successfully
|
|
@@ -304,8 +318,18 @@ class WorkerPool {
|
|
|
304
318
|
// Recycle worker immediately; result restoration happens asynchronously
|
|
305
319
|
this.recycleWorker(workerState);
|
|
306
320
|
|
|
307
|
-
this.serializer.restoreBuffers(result)
|
|
308
|
-
|
|
321
|
+
const restoreResult = this.serializer.restoreBuffers(result);
|
|
322
|
+
const restoreContext = contextUpdates
|
|
323
|
+
? this.serializer.restoreBuffers(contextUpdates)
|
|
324
|
+
: Promise.resolve(contextUpdates);
|
|
325
|
+
|
|
326
|
+
Promise.all([restoreResult, restoreContext]).then(([restoredResult, restoredContext]) => {
|
|
327
|
+
callback(null, {
|
|
328
|
+
result: restoredResult,
|
|
329
|
+
performance: performance || null,
|
|
330
|
+
contextUpdates: restoredContext || null,
|
|
331
|
+
logs: Array.isArray(logs) ? logs : null
|
|
332
|
+
});
|
|
309
333
|
}).catch(restoreErr => {
|
|
310
334
|
callback(restoreErr instanceof Error ? restoreErr : new Error(String(restoreErr)), null);
|
|
311
335
|
});
|
|
@@ -322,10 +346,23 @@ class WorkerPool {
|
|
|
322
346
|
const callback = this.callbacks.get(taskId);
|
|
323
347
|
if (callback) {
|
|
324
348
|
this.callbacks.delete(taskId);
|
|
325
|
-
const err = new Error(error.message);
|
|
326
|
-
err.stack = error.stack;
|
|
327
|
-
err.name = error.name;
|
|
328
|
-
|
|
349
|
+
const err = new Error(error && error.message ? error.message : 'Worker error');
|
|
350
|
+
err.stack = error && error.stack ? error.stack : err.stack;
|
|
351
|
+
err.name = error && error.name ? error.name : err.name;
|
|
352
|
+
|
|
353
|
+
if (contextUpdates) {
|
|
354
|
+
this.serializer.restoreBuffers(contextUpdates).then(restoredContext => {
|
|
355
|
+
err.contextUpdates = restoredContext;
|
|
356
|
+
err.logs = Array.isArray(logs) ? logs : null;
|
|
357
|
+
callback(err, null);
|
|
358
|
+
}).catch(() => {
|
|
359
|
+
err.logs = Array.isArray(logs) ? logs : null;
|
|
360
|
+
callback(err, null);
|
|
361
|
+
});
|
|
362
|
+
} else {
|
|
363
|
+
err.logs = Array.isArray(logs) ? logs : null;
|
|
364
|
+
callback(err, null);
|
|
365
|
+
}
|
|
329
366
|
}
|
|
330
367
|
|
|
331
368
|
// Return worker to idle state
|
|
@@ -16,6 +16,9 @@ const { AsyncMessageSerializer } = require('./message-serializer');
|
|
|
16
16
|
|
|
17
17
|
// Track worker state
|
|
18
18
|
let isTerminating = false;
|
|
19
|
+
const transferMode = workerData && typeof workerData.transferMode === 'string' ? workerData.transferMode : 'transfer';
|
|
20
|
+
const MSG_WRAPPER_KEY = '__rosepetal_msg';
|
|
21
|
+
const CONTEXT_WRAPPER_KEY = '__rosepetal_context';
|
|
19
22
|
|
|
20
23
|
// AsyncLocalStorage for tracking task context across async boundaries
|
|
21
24
|
// This ensures unhandled rejections can be attributed to the correct task
|
|
@@ -77,6 +80,120 @@ const baseRequire = (() => {
|
|
|
77
80
|
|
|
78
81
|
global.require = baseRequire;
|
|
79
82
|
|
|
83
|
+
function createContextProxy(initialData, updates) {
|
|
84
|
+
const data = (initialData && typeof initialData === 'object') ? initialData : {};
|
|
85
|
+
const updateMap = updates || {};
|
|
86
|
+
|
|
87
|
+
const getValue = (key, fallback) => {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(updateMap, key)) {
|
|
89
|
+
return updateMap[key];
|
|
90
|
+
}
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
92
|
+
return data[key];
|
|
93
|
+
}
|
|
94
|
+
return fallback;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const setValue = (key, value) => {
|
|
98
|
+
data[key] = value;
|
|
99
|
+
updateMap[key] = value;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const api = {
|
|
103
|
+
get: (key, storeOrCb, cbMaybe) => {
|
|
104
|
+
let callback = null;
|
|
105
|
+
if (typeof storeOrCb === 'function') {
|
|
106
|
+
callback = storeOrCb;
|
|
107
|
+
} else if (typeof cbMaybe === 'function') {
|
|
108
|
+
callback = cbMaybe;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const value = Array.isArray(key)
|
|
112
|
+
? key.map((entry) => getValue(entry, undefined))
|
|
113
|
+
: getValue(key, undefined);
|
|
114
|
+
|
|
115
|
+
if (typeof callback === 'function') {
|
|
116
|
+
callback(null, value);
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
},
|
|
121
|
+
set: (key, value, cbMaybe) => {
|
|
122
|
+
let callback = typeof cbMaybe === 'function' ? cbMaybe : null;
|
|
123
|
+
let entries = [];
|
|
124
|
+
|
|
125
|
+
if (Array.isArray(key)) {
|
|
126
|
+
if (Array.isArray(value)) {
|
|
127
|
+
entries = key.map((entry, index) => [entry, value[index]]);
|
|
128
|
+
} else if (value && typeof value === 'object') {
|
|
129
|
+
entries = key.map((entry) => [entry, value[entry]]);
|
|
130
|
+
}
|
|
131
|
+
} else if (key && typeof key === 'object' && value !== null) {
|
|
132
|
+
entries = Object.entries(key);
|
|
133
|
+
if (typeof value === 'function') {
|
|
134
|
+
callback = value;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
entries = [[key, value]];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
entries.forEach(([entryKey, entryValue]) => {
|
|
141
|
+
if (entryKey !== undefined) {
|
|
142
|
+
setValue(entryKey, entryValue);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (typeof callback === 'function') {
|
|
147
|
+
callback(null);
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return new Proxy(api, {
|
|
154
|
+
get(target, prop) {
|
|
155
|
+
if (typeof prop === 'symbol' || prop in target) {
|
|
156
|
+
return target[prop];
|
|
157
|
+
}
|
|
158
|
+
return getValue(prop, undefined);
|
|
159
|
+
},
|
|
160
|
+
set(target, prop, value) {
|
|
161
|
+
if (typeof prop === 'symbol' || prop in target) {
|
|
162
|
+
target[prop] = value;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
setValue(prop, value);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function createNodeProxy(logs) {
|
|
172
|
+
const push = (level, args) => {
|
|
173
|
+
if (!Array.isArray(logs)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!args || args.length === 0) {
|
|
177
|
+
logs.push({ level, message: '' });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const message = args.length === 1 ? args[0] : args.map((arg) => arg);
|
|
181
|
+
logs.push({ level, message });
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
warn: (...args) => {
|
|
186
|
+
push('warn', args);
|
|
187
|
+
},
|
|
188
|
+
error: (...args) => {
|
|
189
|
+
push('error', args);
|
|
190
|
+
},
|
|
191
|
+
log: (...args) => {
|
|
192
|
+
push('log', args);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
80
197
|
function parseModuleSpec(spec) {
|
|
81
198
|
const match = /((?:@[^/]+\/)?[^/@]+)(\/[^/@]+)?(?:@([\s\S]+))?/.exec(spec);
|
|
82
199
|
if (!match) {
|
|
@@ -146,54 +263,133 @@ async function initializeWorker() {
|
|
|
146
263
|
// This ensures unhandled rejections from fire-and-forget promises
|
|
147
264
|
// can be traced back to the correct task
|
|
148
265
|
taskContext.run({ taskId }, async () => {
|
|
266
|
+
const contextUpdates = { flow: {}, global: {}, context: {} };
|
|
267
|
+
const logs = [];
|
|
268
|
+
|
|
149
269
|
try {
|
|
150
270
|
// Restore + execute user code
|
|
151
271
|
const restoreStart = process.hrtime.bigint();
|
|
152
|
-
const
|
|
272
|
+
const restoredPayload = await serializer.restoreBuffers(msg);
|
|
153
273
|
const transferToWorkerMs = hrtimeDiffToMs(restoreStart);
|
|
154
274
|
|
|
275
|
+
let contextPayload = {};
|
|
276
|
+
let restoredMsg = restoredPayload;
|
|
277
|
+
if (restoredPayload && typeof restoredPayload === 'object' && Object.prototype.hasOwnProperty.call(restoredPayload, MSG_WRAPPER_KEY)) {
|
|
278
|
+
contextPayload = restoredPayload[CONTEXT_WRAPPER_KEY] || {};
|
|
279
|
+
restoredMsg = restoredPayload[MSG_WRAPPER_KEY];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const nodeProxy = createNodeProxy(logs);
|
|
283
|
+
const flowProxy = createContextProxy(contextPayload.flow, contextUpdates.flow);
|
|
284
|
+
const globalProxy = createContextProxy(contextPayload.global, contextUpdates.global);
|
|
285
|
+
const contextProxy = createContextProxy(contextPayload.context, contextUpdates.context);
|
|
286
|
+
|
|
155
287
|
// Cache key includes code + module vars to handle different module configs
|
|
156
288
|
const cacheKey = code + '|' + moduleVars.join(',');
|
|
157
289
|
let userFunction = getCachedFunction(cacheKey);
|
|
158
290
|
if (!userFunction) {
|
|
159
291
|
// Create function with msg + all module variables as parameters
|
|
160
|
-
userFunction = new AsyncFunction('msg', ...moduleVars, code);
|
|
292
|
+
userFunction = new AsyncFunction('msg', 'node', 'flow', 'global', 'context', ...moduleVars, code);
|
|
161
293
|
setCachedFunction(cacheKey, userFunction);
|
|
162
294
|
}
|
|
163
295
|
|
|
164
296
|
const execStart = process.hrtime.bigint();
|
|
165
297
|
// Execute with msg and all loaded module values
|
|
166
|
-
const rawResult = await userFunction(restoredMsg, ...moduleValues);
|
|
298
|
+
const rawResult = await userFunction(restoredMsg, nodeProxy, flowProxy, globalProxy, contextProxy, ...moduleValues);
|
|
167
299
|
const executionMs = hrtimeDiffToMs(execStart);
|
|
168
300
|
|
|
169
|
-
// Offload buffers in
|
|
301
|
+
// Offload buffers in result + context updates
|
|
170
302
|
const encodeStart = process.hrtime.bigint();
|
|
171
|
-
const
|
|
303
|
+
const transferList = transferMode === 'transfer' ? [] : null;
|
|
304
|
+
const transferSet = transferList ? new Set() : null;
|
|
305
|
+
const bufferCache = new WeakMap();
|
|
306
|
+
|
|
307
|
+
const encodedResult = await serializer.sanitizeMessage(rawResult, null, taskId, {
|
|
308
|
+
transferMode,
|
|
309
|
+
transferList,
|
|
310
|
+
transferSet,
|
|
311
|
+
bufferCache
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const hasUpdates = (
|
|
315
|
+
Object.keys(contextUpdates.flow).length > 0 ||
|
|
316
|
+
Object.keys(contextUpdates.global).length > 0 ||
|
|
317
|
+
Object.keys(contextUpdates.context).length > 0
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const encodedContextUpdates = hasUpdates
|
|
321
|
+
? await serializer.sanitizeMessage(contextUpdates, null, taskId, {
|
|
322
|
+
transferMode,
|
|
323
|
+
transferList,
|
|
324
|
+
transferSet,
|
|
325
|
+
bufferCache
|
|
326
|
+
})
|
|
327
|
+
: null;
|
|
328
|
+
|
|
172
329
|
const transferToMainMs = hrtimeDiffToMs(encodeStart);
|
|
173
330
|
|
|
174
331
|
// Send result back to main thread
|
|
175
|
-
|
|
332
|
+
const payload = {
|
|
176
333
|
type: 'result',
|
|
177
334
|
taskId,
|
|
178
335
|
result: encodedResult,
|
|
336
|
+
contextUpdates: encodedContextUpdates,
|
|
337
|
+
logs: logs.length > 0 ? logs : null,
|
|
179
338
|
performance: {
|
|
180
339
|
transferToWorkerMs,
|
|
181
340
|
executionMs,
|
|
182
341
|
transferToMainMs
|
|
183
342
|
}
|
|
184
|
-
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (transferList && transferList.length > 0) {
|
|
346
|
+
parentPort.postMessage(payload, transferList);
|
|
347
|
+
} else {
|
|
348
|
+
parentPort.postMessage(payload);
|
|
349
|
+
}
|
|
185
350
|
|
|
186
351
|
} catch (err) {
|
|
187
352
|
// Send error back to main thread
|
|
188
|
-
|
|
353
|
+
const transferList = transferMode === 'transfer' ? [] : null;
|
|
354
|
+
const transferSet = transferList ? new Set() : null;
|
|
355
|
+
const bufferCache = new WeakMap();
|
|
356
|
+
const hasUpdates = (
|
|
357
|
+
Object.keys(contextUpdates.flow).length > 0 ||
|
|
358
|
+
Object.keys(contextUpdates.global).length > 0 ||
|
|
359
|
+
Object.keys(contextUpdates.context).length > 0
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
let encodedContextUpdates = null;
|
|
363
|
+
if (hasUpdates) {
|
|
364
|
+
try {
|
|
365
|
+
encodedContextUpdates = await serializer.sanitizeMessage(contextUpdates, null, taskId, {
|
|
366
|
+
transferMode,
|
|
367
|
+
transferList,
|
|
368
|
+
transferSet,
|
|
369
|
+
bufferCache
|
|
370
|
+
});
|
|
371
|
+
} catch (_encodeErr) {
|
|
372
|
+
encodedContextUpdates = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const payload = {
|
|
189
377
|
type: 'error',
|
|
190
378
|
taskId,
|
|
191
379
|
error: {
|
|
192
380
|
message: err.message,
|
|
193
381
|
stack: err.stack,
|
|
194
382
|
name: err.name
|
|
195
|
-
}
|
|
196
|
-
|
|
383
|
+
},
|
|
384
|
+
contextUpdates: encodedContextUpdates,
|
|
385
|
+
logs: logs.length > 0 ? logs : null
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (transferList && transferList.length > 0) {
|
|
389
|
+
parentPort.postMessage(payload, transferList);
|
|
390
|
+
} else {
|
|
391
|
+
parentPort.postMessage(payload);
|
|
392
|
+
}
|
|
197
393
|
}
|
|
198
394
|
});
|
|
199
395
|
} else if (type === 'terminate') {
|
package/package.json
CHANGED