tywrap 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +1 -3
- package/dist/tywrap.js.map +1 -1
- package/package.json +1 -4
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/src/tywrap.ts +1 -3
- package/dist/core/analyzer.d.ts +0 -64
- package/dist/core/analyzer.d.ts.map +0 -1
- package/dist/core/analyzer.js +0 -710
- package/dist/core/analyzer.js.map +0 -1
- package/dist/utils/parallel-processor.d.ts +0 -146
- package/dist/utils/parallel-processor.d.ts.map +0 -1
- package/dist/utils/parallel-processor.js +0 -707
- package/dist/utils/parallel-processor.js.map +0 -1
- package/src/core/analyzer.ts +0 -824
- package/src/utils/parallel-processor.ts +0 -955
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel Processing System for tywrap
|
|
3
|
-
* High-performance parallel processing for large Python codebases
|
|
4
|
-
*/
|
|
5
|
-
import { EventEmitter } from 'events';
|
|
6
|
-
import { cpus } from 'os';
|
|
7
|
-
import { performance } from 'perf_hooks';
|
|
8
|
-
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { CodeGenerator } from '../core/generator.js';
|
|
11
|
-
import { PyAnalyzer } from '../core/analyzer.js';
|
|
12
|
-
import { globalCache } from './cache.js';
|
|
13
|
-
import { getComponentLogger } from './logger.js';
|
|
14
|
-
const log = getComponentLogger('ParallelProcessor');
|
|
15
|
-
export class ParallelProcessor extends EventEmitter {
|
|
16
|
-
workers = new Map();
|
|
17
|
-
workerStats = new Map();
|
|
18
|
-
taskQueue = [];
|
|
19
|
-
queueInterval;
|
|
20
|
-
activeTasks = new Map();
|
|
21
|
-
pendingResults = new Map();
|
|
22
|
-
roundRobinIndex = 0;
|
|
23
|
-
options;
|
|
24
|
-
disposed = false;
|
|
25
|
-
debug = false;
|
|
26
|
-
constructor(options = {}) {
|
|
27
|
-
super();
|
|
28
|
-
const defaultWorkerScript = fileURLToPath(import.meta.url);
|
|
29
|
-
this.options = {
|
|
30
|
-
maxWorkers: options.maxWorkers ?? Math.min(cpus().length, 8),
|
|
31
|
-
taskTimeout: options.taskTimeout ?? 30000,
|
|
32
|
-
retryAttempts: options.retryAttempts ?? 2,
|
|
33
|
-
enableMemoryMonitoring: options.enableMemoryMonitoring ?? true,
|
|
34
|
-
enableCaching: options.enableCaching ?? true,
|
|
35
|
-
workerScript: options.workerScript ?? defaultWorkerScript,
|
|
36
|
-
batchSize: options.batchSize ?? 1,
|
|
37
|
-
loadBalancing: options.loadBalancing ?? 'least-loaded',
|
|
38
|
-
debug: options.debug ?? false,
|
|
39
|
-
};
|
|
40
|
-
this.debug = this.options.debug;
|
|
41
|
-
if (this.debug) {
|
|
42
|
-
this.debugLog(`🚀 Parallel processor initialized with ${this.options.maxWorkers} workers`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
debugLog(message) {
|
|
46
|
-
if (!this.debug) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
process.stdout.write(`${message}\n`);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Initialize worker pool
|
|
53
|
-
*/
|
|
54
|
-
async init() {
|
|
55
|
-
if (this.disposed) {
|
|
56
|
-
throw new Error('Processor has been disposed');
|
|
57
|
-
}
|
|
58
|
-
// Spawn initial worker pool
|
|
59
|
-
for (let i = 0; i < this.options.maxWorkers; i++) {
|
|
60
|
-
await this.spawnWorker(`worker_${i}`);
|
|
61
|
-
}
|
|
62
|
-
// Start task processing
|
|
63
|
-
this.processQueue();
|
|
64
|
-
this.debugLog(`✅ Worker pool initialized with ${this.workers.size} workers`);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Process Python module analysis in parallel
|
|
68
|
-
*/
|
|
69
|
-
async analyzeModulesParallel(sources, options = {}) {
|
|
70
|
-
const chunkSize = options.chunkSize ?? Math.ceil(sources.length / this.options.maxWorkers);
|
|
71
|
-
const chunks = this.chunkArray(sources, chunkSize);
|
|
72
|
-
this.debugLog(`📊 Analyzing ${sources.length} modules in ${chunks.length} chunks`);
|
|
73
|
-
const tasks = chunks.map((chunk, index) => ({
|
|
74
|
-
id: `analyze_chunk_${index}`,
|
|
75
|
-
type: 'analyze',
|
|
76
|
-
data: { sources: chunk },
|
|
77
|
-
priority: 1,
|
|
78
|
-
}));
|
|
79
|
-
const results = await this.executeTasks(tasks);
|
|
80
|
-
// Flatten chunked results
|
|
81
|
-
const flatResults = [];
|
|
82
|
-
for (const result of results) {
|
|
83
|
-
if (!result.success || !result.result) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (result.success && result.result) {
|
|
87
|
-
const chunkResults = result.result;
|
|
88
|
-
if (Array.isArray(chunkResults)) {
|
|
89
|
-
flatResults.push(...chunkResults);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return flatResults;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Generate TypeScript wrappers in parallel
|
|
97
|
-
*/
|
|
98
|
-
async generateWrappersParallel(modules, options = {}) {
|
|
99
|
-
const chunkSize = options.chunkSize ?? Math.ceil(modules.length / this.options.maxWorkers);
|
|
100
|
-
const chunks = this.chunkArray(modules, chunkSize);
|
|
101
|
-
this.debugLog(`🏗️ Generating ${modules.length} wrappers in ${chunks.length} chunks`);
|
|
102
|
-
const tasks = chunks.map((chunk, index) => ({
|
|
103
|
-
id: `generate_chunk_${index}`,
|
|
104
|
-
type: 'generate',
|
|
105
|
-
data: { modules: chunk },
|
|
106
|
-
priority: 1,
|
|
107
|
-
}));
|
|
108
|
-
const results = await this.executeTasks(tasks);
|
|
109
|
-
// Flatten chunked results
|
|
110
|
-
const flatResults = [];
|
|
111
|
-
for (const result of results) {
|
|
112
|
-
if (result.success && result.result) {
|
|
113
|
-
const chunkResults = result.result;
|
|
114
|
-
if (Array.isArray(chunkResults)) {
|
|
115
|
-
flatResults.push(...chunkResults);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return flatResults;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Execute tasks in parallel with load balancing
|
|
123
|
-
*/
|
|
124
|
-
async executeTasks(tasks) {
|
|
125
|
-
if (this.workers.size === 0) {
|
|
126
|
-
await this.init();
|
|
127
|
-
}
|
|
128
|
-
// Sort tasks by priority
|
|
129
|
-
const sortedTasks = [...tasks].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
130
|
-
// Execute tasks with batching
|
|
131
|
-
const results = [];
|
|
132
|
-
const inFlight = new Set();
|
|
133
|
-
const maxInFlight = this.workers.size * 2;
|
|
134
|
-
for (const task of sortedTasks) {
|
|
135
|
-
const promise = this.executeTask(task);
|
|
136
|
-
inFlight.add(promise);
|
|
137
|
-
promise
|
|
138
|
-
.finally(() => {
|
|
139
|
-
inFlight.delete(promise);
|
|
140
|
-
})
|
|
141
|
-
.catch(() => {
|
|
142
|
-
// no-op: individual task failures are encoded in results
|
|
143
|
-
});
|
|
144
|
-
// Limit concurrent tasks to prevent overwhelming workers
|
|
145
|
-
if (inFlight.size >= maxInFlight) {
|
|
146
|
-
const completed = await Promise.race(inFlight);
|
|
147
|
-
results.push(completed);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
// Wait for remaining tasks
|
|
151
|
-
const remaining = await Promise.all(inFlight);
|
|
152
|
-
results.push(...remaining);
|
|
153
|
-
return results;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Execute single task
|
|
157
|
-
*/
|
|
158
|
-
async executeTask(task) {
|
|
159
|
-
// Check cache first if enabled
|
|
160
|
-
if (this.options.enableCaching) {
|
|
161
|
-
const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
|
|
162
|
-
const cached = await globalCache.get(cacheKey);
|
|
163
|
-
if (cached) {
|
|
164
|
-
this.debugLog(`🎯 Cache HIT for task ${task.id}`);
|
|
165
|
-
return cached;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
let attempts = 0;
|
|
169
|
-
let lastError;
|
|
170
|
-
while (attempts < this.options.retryAttempts) {
|
|
171
|
-
attempts++;
|
|
172
|
-
try {
|
|
173
|
-
const selected = this.selectOptimalWorker();
|
|
174
|
-
if (!selected) {
|
|
175
|
-
throw new Error('No available workers');
|
|
176
|
-
}
|
|
177
|
-
const result = await this.sendTaskToWorker(selected.workerId, selected.worker, task);
|
|
178
|
-
// Cache successful results
|
|
179
|
-
if (this.options.enableCaching && result.success) {
|
|
180
|
-
const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
|
|
181
|
-
await globalCache.set(cacheKey, result, { computeTime: result.duration });
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
lastError = error;
|
|
187
|
-
log.warn('Task failed', { taskId: task.id, attempt: attempts, error: String(error) });
|
|
188
|
-
if (attempts < this.options.retryAttempts) {
|
|
189
|
-
// Exponential backoff
|
|
190
|
-
await this.sleep(Math.pow(2, attempts) * 1000);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Return failure result
|
|
195
|
-
return {
|
|
196
|
-
taskId: task.id,
|
|
197
|
-
success: false,
|
|
198
|
-
error: lastError?.message ?? 'Task failed after retries',
|
|
199
|
-
duration: 0,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Select optimal worker using load balancing strategy
|
|
204
|
-
*/
|
|
205
|
-
selectOptimalWorker() {
|
|
206
|
-
const availableWorkers = Array.from(this.workers.entries())
|
|
207
|
-
.map(([workerId, worker]) => ({ workerId, worker }))
|
|
208
|
-
.filter(({ workerId, worker }) => {
|
|
209
|
-
if (worker.threadId <= 0) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
const stats = this.workerStats.get(workerId);
|
|
213
|
-
return stats?.isActive !== false;
|
|
214
|
-
});
|
|
215
|
-
if (availableWorkers.length === 0) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
switch (this.options.loadBalancing) {
|
|
219
|
-
case 'round-robin': {
|
|
220
|
-
const selected = availableWorkers[this.roundRobinIndex % availableWorkers.length];
|
|
221
|
-
this.roundRobinIndex = (this.roundRobinIndex + 1) % availableWorkers.length;
|
|
222
|
-
return selected ?? null;
|
|
223
|
-
}
|
|
224
|
-
case 'least-loaded': {
|
|
225
|
-
// Find worker with least active tasks
|
|
226
|
-
const workerLoads = availableWorkers.map(({ workerId, worker }) => {
|
|
227
|
-
const stats = this.workerStats.get(workerId);
|
|
228
|
-
return { workerId, worker, load: stats?.tasksCompleted ?? 0 };
|
|
229
|
-
});
|
|
230
|
-
workerLoads.sort((a, b) => a.load - b.load);
|
|
231
|
-
const first = workerLoads[0];
|
|
232
|
-
return first ? { workerId: first.workerId, worker: first.worker } : null;
|
|
233
|
-
}
|
|
234
|
-
case 'weighted': {
|
|
235
|
-
// Weight by average task completion time
|
|
236
|
-
const workerWeights = availableWorkers.map(({ workerId, worker }) => {
|
|
237
|
-
const stats = this.workerStats.get(workerId);
|
|
238
|
-
const avgTime = stats?.averageTime ?? 1000;
|
|
239
|
-
return { workerId, worker, weight: 1 / avgTime };
|
|
240
|
-
});
|
|
241
|
-
// Weighted random selection
|
|
242
|
-
const totalWeight = workerWeights.reduce((sum, w) => sum + w.weight, 0);
|
|
243
|
-
let random = Math.random() * totalWeight;
|
|
244
|
-
for (const { workerId, worker: candidate, weight } of workerWeights) {
|
|
245
|
-
random -= weight;
|
|
246
|
-
if (random <= 0) {
|
|
247
|
-
return { workerId, worker: candidate };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const fallback = workerWeights[0];
|
|
251
|
-
return fallback ? { workerId: fallback.workerId, worker: fallback.worker } : null;
|
|
252
|
-
}
|
|
253
|
-
default:
|
|
254
|
-
return availableWorkers[0] ?? null;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Send task to specific worker
|
|
259
|
-
*/
|
|
260
|
-
async sendTaskToWorker(workerId, worker, task) {
|
|
261
|
-
return new Promise((resolve, reject) => {
|
|
262
|
-
if (this.disposed) {
|
|
263
|
-
reject(new Error('Processor has been disposed'));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const timeoutMs = task.timeout ?? this.options.taskTimeout;
|
|
267
|
-
const timeout = setTimeout(() => {
|
|
268
|
-
const pending = this.pendingResults.get(task.id);
|
|
269
|
-
if (!pending) {
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
this.pendingResults.delete(task.id);
|
|
273
|
-
this.activeTasks.delete(task.id);
|
|
274
|
-
pending.reject(new Error(`Task ${task.id} timed out after ${timeoutMs}ms`));
|
|
275
|
-
}, timeoutMs);
|
|
276
|
-
this.pendingResults.set(task.id, {
|
|
277
|
-
workerId,
|
|
278
|
-
resolve: resolve,
|
|
279
|
-
reject,
|
|
280
|
-
timeout,
|
|
281
|
-
});
|
|
282
|
-
this.activeTasks.set(task.id, task);
|
|
283
|
-
try {
|
|
284
|
-
worker.postMessage({
|
|
285
|
-
type: 'task',
|
|
286
|
-
task,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
this.pendingResults.delete(task.id);
|
|
291
|
-
this.activeTasks.delete(task.id);
|
|
292
|
-
clearTimeout(timeout);
|
|
293
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Spawn new worker
|
|
299
|
-
*/
|
|
300
|
-
async spawnWorker(workerId) {
|
|
301
|
-
const worker = new Worker(this.options.workerScript, {
|
|
302
|
-
workerData: {
|
|
303
|
-
workerId,
|
|
304
|
-
options: {
|
|
305
|
-
enableMemoryMonitoring: this.options.enableMemoryMonitoring,
|
|
306
|
-
enableCaching: this.options.enableCaching,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
// Setup worker message handling
|
|
311
|
-
worker.on('message', message => {
|
|
312
|
-
this.handleWorkerMessage(workerId, message);
|
|
313
|
-
});
|
|
314
|
-
// Setup worker error handling
|
|
315
|
-
worker.on('error', error => {
|
|
316
|
-
const workerError = error instanceof Error ? error : new Error(String(error));
|
|
317
|
-
log.error('Worker error', { workerId, error: String(workerError) });
|
|
318
|
-
this.handleWorkerError(workerId, workerError);
|
|
319
|
-
});
|
|
320
|
-
// Setup worker exit handling
|
|
321
|
-
worker.on('exit', code => {
|
|
322
|
-
log.warn('Worker exited', { workerId, code });
|
|
323
|
-
this.handleWorkerExit(workerId, code);
|
|
324
|
-
});
|
|
325
|
-
this.workers.set(workerId, worker);
|
|
326
|
-
this.workerStats.set(workerId, {
|
|
327
|
-
workerId,
|
|
328
|
-
tasksCompleted: 0,
|
|
329
|
-
totalTime: 0,
|
|
330
|
-
averageTime: 0,
|
|
331
|
-
errorCount: 0,
|
|
332
|
-
memoryPeak: 0,
|
|
333
|
-
isActive: true,
|
|
334
|
-
});
|
|
335
|
-
this.debugLog(`👷 Spawned worker ${workerId}`);
|
|
336
|
-
return worker;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Handle message from worker
|
|
340
|
-
*/
|
|
341
|
-
handleWorkerMessage(workerId, message) {
|
|
342
|
-
if (message.type === 'task_complete') {
|
|
343
|
-
const { taskId, result, error, duration, memoryUsage } = message;
|
|
344
|
-
const pending = this.pendingResults.get(taskId);
|
|
345
|
-
if (pending) {
|
|
346
|
-
this.pendingResults.delete(taskId);
|
|
347
|
-
this.activeTasks.delete(taskId);
|
|
348
|
-
if (pending.timeout) {
|
|
349
|
-
clearTimeout(pending.timeout);
|
|
350
|
-
}
|
|
351
|
-
// Update worker stats
|
|
352
|
-
const stats = this.workerStats.get(workerId);
|
|
353
|
-
if (stats) {
|
|
354
|
-
stats.tasksCompleted++;
|
|
355
|
-
stats.totalTime += duration;
|
|
356
|
-
stats.averageTime = stats.totalTime / stats.tasksCompleted;
|
|
357
|
-
if (error) {
|
|
358
|
-
stats.errorCount++;
|
|
359
|
-
}
|
|
360
|
-
if (memoryUsage && memoryUsage > stats.memoryPeak) {
|
|
361
|
-
stats.memoryPeak = memoryUsage;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const parallelResult = {
|
|
365
|
-
taskId,
|
|
366
|
-
success: !error,
|
|
367
|
-
result,
|
|
368
|
-
error,
|
|
369
|
-
duration,
|
|
370
|
-
memoryUsage,
|
|
371
|
-
};
|
|
372
|
-
pending.resolve(parallelResult);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
else if (message.type === 'worker_ready') {
|
|
376
|
-
this.emit('worker_ready', workerId);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Handle worker error
|
|
381
|
-
*/
|
|
382
|
-
handleWorkerError(workerId, error) {
|
|
383
|
-
const stats = this.workerStats.get(workerId);
|
|
384
|
-
if (stats) {
|
|
385
|
-
stats.errorCount++;
|
|
386
|
-
stats.isActive = false;
|
|
387
|
-
}
|
|
388
|
-
// Reject only tasks owned by this worker.
|
|
389
|
-
const ownedTasks = [];
|
|
390
|
-
for (const [taskId, pending] of this.pendingResults) {
|
|
391
|
-
if (pending.workerId === workerId) {
|
|
392
|
-
ownedTasks.push(taskId);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
for (const taskId of ownedTasks) {
|
|
396
|
-
const pending = this.pendingResults.get(taskId);
|
|
397
|
-
if (!pending) {
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
if (pending.timeout) {
|
|
401
|
-
clearTimeout(pending.timeout);
|
|
402
|
-
}
|
|
403
|
-
pending.reject(new Error(`Worker ${workerId} error: ${error.message}`));
|
|
404
|
-
this.pendingResults.delete(taskId);
|
|
405
|
-
this.activeTasks.delete(taskId);
|
|
406
|
-
}
|
|
407
|
-
this.emit('worker_error', workerId, error);
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Handle worker exit
|
|
411
|
-
*/
|
|
412
|
-
handleWorkerExit(workerId, code) {
|
|
413
|
-
this.workers.delete(workerId);
|
|
414
|
-
const stats = this.workerStats.get(workerId);
|
|
415
|
-
if (stats) {
|
|
416
|
-
stats.isActive = false;
|
|
417
|
-
}
|
|
418
|
-
// Reject any tasks that were in-flight on this worker.
|
|
419
|
-
const ownedTasks = [];
|
|
420
|
-
for (const [taskId, pending] of this.pendingResults) {
|
|
421
|
-
if (pending.workerId === workerId) {
|
|
422
|
-
ownedTasks.push(taskId);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
for (const taskId of ownedTasks) {
|
|
426
|
-
const pending = this.pendingResults.get(taskId);
|
|
427
|
-
if (!pending) {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (pending.timeout) {
|
|
431
|
-
clearTimeout(pending.timeout);
|
|
432
|
-
}
|
|
433
|
-
pending.reject(new Error(`Worker ${workerId} exited with code ${code}`));
|
|
434
|
-
this.pendingResults.delete(taskId);
|
|
435
|
-
this.activeTasks.delete(taskId);
|
|
436
|
-
}
|
|
437
|
-
// Respawn worker if not disposing
|
|
438
|
-
if (!this.disposed && this.workers.size < this.options.maxWorkers) {
|
|
439
|
-
this.spawnWorker(`${workerId}_respawn_${Date.now()}`).catch(error => {
|
|
440
|
-
log.error('Failed to respawn worker', { workerId, error: String(error) });
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
this.emit('worker_exit', workerId, code);
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Process task queue
|
|
447
|
-
*/
|
|
448
|
-
processQueue() {
|
|
449
|
-
// Simple queue processing - could be enhanced with priority scheduling
|
|
450
|
-
if (this.queueInterval) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
this.queueInterval = setInterval(() => {
|
|
454
|
-
if (this.taskQueue.length > 0 && this.workers.size > 0) {
|
|
455
|
-
const task = this.taskQueue.shift();
|
|
456
|
-
if (task) {
|
|
457
|
-
this.executeTask(task).catch(error => {
|
|
458
|
-
log.error('Queue task execution failed', { error: String(error) });
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}, 100);
|
|
463
|
-
// Don't keep the event loop alive just for an idle queue pump.
|
|
464
|
-
this.queueInterval.unref?.();
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Get worker statistics
|
|
468
|
-
*/
|
|
469
|
-
getWorkerStats() {
|
|
470
|
-
return Array.from(this.workerStats.values());
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Get overall processing statistics
|
|
474
|
-
*/
|
|
475
|
-
getStats() {
|
|
476
|
-
const workers = Array.from(this.workerStats.values());
|
|
477
|
-
const totalTasks = workers.reduce((sum, w) => sum + w.tasksCompleted, 0);
|
|
478
|
-
const totalErrors = workers.reduce((sum, w) => sum + w.errorCount, 0);
|
|
479
|
-
const totalTime = workers.reduce((sum, w) => sum + w.totalTime, 0);
|
|
480
|
-
const avgTime = totalTasks > 0 ? totalTime / totalTasks : 0;
|
|
481
|
-
return {
|
|
482
|
-
activeWorkers: workers.filter(w => w.isActive).length,
|
|
483
|
-
totalWorkers: workers.length,
|
|
484
|
-
tasksCompleted: totalTasks,
|
|
485
|
-
totalErrors,
|
|
486
|
-
averageTaskTime: avgTime,
|
|
487
|
-
queueLength: this.taskQueue.length,
|
|
488
|
-
activeTasks: this.activeTasks.size,
|
|
489
|
-
workerStats: workers,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Utility methods
|
|
494
|
-
*/
|
|
495
|
-
chunkArray(array, chunkSize) {
|
|
496
|
-
const chunks = [];
|
|
497
|
-
for (let i = 0; i < array.length; i += chunkSize) {
|
|
498
|
-
chunks.push(array.slice(i, i + chunkSize));
|
|
499
|
-
}
|
|
500
|
-
return chunks;
|
|
501
|
-
}
|
|
502
|
-
sleep(ms) {
|
|
503
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Gracefully dispose all workers
|
|
507
|
-
*/
|
|
508
|
-
async dispose() {
|
|
509
|
-
if (this.disposed) {
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
this.disposed = true;
|
|
513
|
-
this.debugLog('🛑 Disposing parallel processor...');
|
|
514
|
-
if (this.queueInterval) {
|
|
515
|
-
clearInterval(this.queueInterval);
|
|
516
|
-
this.queueInterval = undefined;
|
|
517
|
-
}
|
|
518
|
-
// Reject any pending results immediately so callers don't hang.
|
|
519
|
-
const disposedError = new Error('Processor has been disposed');
|
|
520
|
-
for (const pending of this.pendingResults.values()) {
|
|
521
|
-
if (pending.timeout) {
|
|
522
|
-
clearTimeout(pending.timeout);
|
|
523
|
-
}
|
|
524
|
-
pending.reject(disposedError);
|
|
525
|
-
}
|
|
526
|
-
this.pendingResults.clear();
|
|
527
|
-
this.activeTasks.clear();
|
|
528
|
-
// Terminate all workers
|
|
529
|
-
const terminationPromises = Array.from(this.workers.values()).map(worker => {
|
|
530
|
-
return new Promise(resolve => {
|
|
531
|
-
const timeout = setTimeout(() => {
|
|
532
|
-
worker.terminate();
|
|
533
|
-
resolve();
|
|
534
|
-
}, 5000);
|
|
535
|
-
worker.once('exit', () => {
|
|
536
|
-
clearTimeout(timeout);
|
|
537
|
-
resolve();
|
|
538
|
-
});
|
|
539
|
-
try {
|
|
540
|
-
worker.postMessage({ type: 'shutdown' });
|
|
541
|
-
}
|
|
542
|
-
catch {
|
|
543
|
-
// Ignore: worker may already be dead.
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
await Promise.all(terminationPromises);
|
|
548
|
-
// Clear data structures
|
|
549
|
-
this.workers.clear();
|
|
550
|
-
this.workerStats.clear();
|
|
551
|
-
this.taskQueue.length = 0;
|
|
552
|
-
this.removeAllListeners();
|
|
553
|
-
this.debugLog('✅ Parallel processor disposed');
|
|
554
|
-
}
|
|
555
|
-
setDebug(debug) {
|
|
556
|
-
this.debug = debug;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
// Worker script implementation (this runs in worker threads)
|
|
560
|
-
if (!isMainThread && parentPort) {
|
|
561
|
-
const port = parentPort;
|
|
562
|
-
const { workerId } = workerData;
|
|
563
|
-
let analyzer = null;
|
|
564
|
-
let analyzerInitialized = false;
|
|
565
|
-
let generator = null;
|
|
566
|
-
const getAnalyzer = async () => {
|
|
567
|
-
analyzer ??= new PyAnalyzer();
|
|
568
|
-
if (!analyzerInitialized) {
|
|
569
|
-
await analyzer.initialize();
|
|
570
|
-
analyzerInitialized = true;
|
|
571
|
-
}
|
|
572
|
-
return analyzer;
|
|
573
|
-
};
|
|
574
|
-
const getGenerator = () => {
|
|
575
|
-
generator ??= new CodeGenerator();
|
|
576
|
-
return generator;
|
|
577
|
-
};
|
|
578
|
-
const createResultId = (prefix, name, index) => `${prefix}:${name}:${index}`;
|
|
579
|
-
// Import required modules in worker context
|
|
580
|
-
const workerImplementation = {
|
|
581
|
-
async processTask(task) {
|
|
582
|
-
const startTime = performance.now();
|
|
583
|
-
let result;
|
|
584
|
-
let error;
|
|
585
|
-
try {
|
|
586
|
-
switch (task.type) {
|
|
587
|
-
case 'analyze':
|
|
588
|
-
result = await this.processAnalysisTask(task.data);
|
|
589
|
-
break;
|
|
590
|
-
case 'generate':
|
|
591
|
-
result = await this.processGenerationTask(task.data);
|
|
592
|
-
break;
|
|
593
|
-
case 'validate':
|
|
594
|
-
result = await this.processValidationTask(task.data);
|
|
595
|
-
break;
|
|
596
|
-
default:
|
|
597
|
-
throw new Error(`Unknown task type: ${task.type}`);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
catch (err) {
|
|
601
|
-
error = String(err);
|
|
602
|
-
}
|
|
603
|
-
const duration = performance.now() - startTime;
|
|
604
|
-
const memoryUsage = process.memoryUsage().heapUsed;
|
|
605
|
-
return { result, error, duration, memoryUsage };
|
|
606
|
-
},
|
|
607
|
-
async processAnalysisTask(data) {
|
|
608
|
-
const results = [];
|
|
609
|
-
const activeAnalyzer = await getAnalyzer();
|
|
610
|
-
for (const [index, source] of data.sources.entries()) {
|
|
611
|
-
const start = performance.now();
|
|
612
|
-
try {
|
|
613
|
-
const modulePath = source.path ?? `${source.name}.py`;
|
|
614
|
-
const analysis = await activeAnalyzer.analyzePythonModule(source.content, modulePath);
|
|
615
|
-
results.push({
|
|
616
|
-
taskId: createResultId('analyze', source.name, index),
|
|
617
|
-
success: true,
|
|
618
|
-
result: analysis,
|
|
619
|
-
duration: performance.now() - start,
|
|
620
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
results.push({
|
|
625
|
-
taskId: createResultId('analyze', source.name, index),
|
|
626
|
-
success: false,
|
|
627
|
-
error: String(error),
|
|
628
|
-
duration: performance.now() - start,
|
|
629
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return results;
|
|
634
|
-
},
|
|
635
|
-
async processGenerationTask(data) {
|
|
636
|
-
const results = [];
|
|
637
|
-
const activeGenerator = getGenerator();
|
|
638
|
-
for (const [index, moduleData] of data.modules.entries()) {
|
|
639
|
-
const start = performance.now();
|
|
640
|
-
try {
|
|
641
|
-
const generationOptions = {
|
|
642
|
-
moduleName: moduleData.name,
|
|
643
|
-
...moduleData.options,
|
|
644
|
-
};
|
|
645
|
-
const generated = await activeGenerator.generateModule(moduleData.module, generationOptions);
|
|
646
|
-
results.push({
|
|
647
|
-
taskId: createResultId('generate', moduleData.name, index),
|
|
648
|
-
success: true,
|
|
649
|
-
result: generated,
|
|
650
|
-
duration: performance.now() - start,
|
|
651
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
catch (error) {
|
|
655
|
-
results.push({
|
|
656
|
-
taskId: createResultId('generate', moduleData.name, index),
|
|
657
|
-
success: false,
|
|
658
|
-
error: String(error),
|
|
659
|
-
duration: performance.now() - start,
|
|
660
|
-
memoryUsage: process.memoryUsage().heapUsed,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
return results;
|
|
665
|
-
},
|
|
666
|
-
async processValidationTask(_data) {
|
|
667
|
-
// Validation logic would go here
|
|
668
|
-
return { validated: true };
|
|
669
|
-
},
|
|
670
|
-
};
|
|
671
|
-
port.on('message', async (raw) => {
|
|
672
|
-
const message = raw;
|
|
673
|
-
if (!message || typeof message !== 'object') {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (message.type === 'task') {
|
|
677
|
-
try {
|
|
678
|
-
const taskResult = await workerImplementation.processTask(message.task);
|
|
679
|
-
port.postMessage({
|
|
680
|
-
type: 'task_complete',
|
|
681
|
-
taskId: message.task.id,
|
|
682
|
-
...(typeof taskResult === 'object' && taskResult !== null
|
|
683
|
-
? taskResult
|
|
684
|
-
: { result: taskResult }),
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
catch (error) {
|
|
688
|
-
port.postMessage({
|
|
689
|
-
type: 'task_complete',
|
|
690
|
-
taskId: message.task.id,
|
|
691
|
-
error: String(error),
|
|
692
|
-
duration: 0,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
else if (message.type === 'shutdown') {
|
|
697
|
-
process.exit(0);
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
// Signal that worker is ready
|
|
701
|
-
port.postMessage({
|
|
702
|
-
type: 'worker_ready',
|
|
703
|
-
workerId,
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
export const globalParallelProcessor = new ParallelProcessor();
|
|
707
|
-
//# sourceMappingURL=parallel-processor.js.map
|