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.
@@ -1,955 +0,0 @@
1
- /**
2
- * Parallel Processing System for tywrap
3
- * High-performance parallel processing for large Python codebases
4
- */
5
-
6
- import { EventEmitter } from 'events';
7
- import { cpus } from 'os';
8
- import { performance } from 'perf_hooks';
9
- import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
10
- import { fileURLToPath } from 'node:url';
11
-
12
- import type { AnalysisResult, PythonModule, GeneratedCode } from '../types/index.js';
13
-
14
- import { CodeGenerator } from '../core/generator.js';
15
- import { PyAnalyzer } from '../core/analyzer.js';
16
- import { globalCache } from './cache.js';
17
- import { getComponentLogger } from './logger.js';
18
-
19
- const log = getComponentLogger('ParallelProcessor');
20
-
21
- export interface ParallelTask<Data = unknown> {
22
- id: string;
23
- type: 'analyze' | 'generate' | 'validate' | 'custom';
24
- data: Data;
25
- options?: Record<string, unknown>;
26
- priority?: number;
27
- timeout?: number;
28
- }
29
-
30
- export interface ParallelResult<T = unknown> {
31
- taskId: string;
32
- success: boolean;
33
- result?: T;
34
- error?: string;
35
- duration: number;
36
- memoryUsage?: number;
37
- }
38
-
39
- export interface WorkerStats {
40
- workerId: string;
41
- tasksCompleted: number;
42
- totalTime: number;
43
- averageTime: number;
44
- errorCount: number;
45
- memoryPeak: number;
46
- isActive: boolean;
47
- }
48
-
49
- export interface ParallelProcessorOptions {
50
- maxWorkers?: number;
51
- taskTimeout?: number; // milliseconds
52
- retryAttempts?: number;
53
- enableMemoryMonitoring?: boolean;
54
- enableCaching?: boolean;
55
- workerScript?: string; // Custom worker script path
56
- batchSize?: number; // Number of tasks to batch per worker
57
- loadBalancing?: 'round-robin' | 'least-loaded' | 'weighted';
58
- debug?: boolean;
59
- }
60
-
61
- export interface ParallelProcessorStats {
62
- activeWorkers: number;
63
- totalWorkers: number;
64
- tasksCompleted: number;
65
- totalErrors: number;
66
- averageTaskTime: number;
67
- queueLength: number;
68
- activeTasks: number;
69
- workerStats: WorkerStats[];
70
- }
71
-
72
- interface AnalyzeTaskData {
73
- sources: Array<{ name: string; content: string; path?: string }>;
74
- }
75
-
76
- interface GenerationTaskData {
77
- modules: Array<{ name: string; module: PythonModule; options?: Record<string, unknown> }>;
78
- }
79
-
80
- type ValidationTaskData = unknown;
81
-
82
- interface AnalyzeTaskResult extends Array<ParallelResult<AnalysisResult>> {}
83
- interface GenerateTaskResult extends Array<ParallelResult<GeneratedCode>> {}
84
- interface ValidateTaskResult {
85
- validated: true;
86
- }
87
-
88
- type AnalyzeTask = ParallelTask<AnalyzeTaskData> & { type: 'analyze' };
89
- type GenerateTask = ParallelTask<GenerationTaskData> & { type: 'generate' };
90
- type ValidateTask = ParallelTask<ValidationTaskData> & { type: 'validate' };
91
- type CustomTask = ParallelTask<unknown> & { type: 'custom' };
92
-
93
- type WorkerTask = AnalyzeTask | GenerateTask | ValidateTask | CustomTask;
94
-
95
- interface WorkerTaskResult {
96
- result: unknown;
97
- error?: string;
98
- duration: number;
99
- memoryUsage: number;
100
- }
101
-
102
- type WorkerMessage =
103
- | {
104
- type: 'task_complete';
105
- taskId: string;
106
- result?: unknown;
107
- error?: string;
108
- duration: number;
109
- memoryUsage?: number;
110
- }
111
- | {
112
- type: 'worker_ready';
113
- workerId: string;
114
- };
115
-
116
- type WorkerControlMessage = { type: 'task'; task: WorkerTask } | { type: 'shutdown' };
117
-
118
- interface WorkerData {
119
- workerId: string;
120
- options?: {
121
- enableMemoryMonitoring: boolean;
122
- enableCaching: boolean;
123
- };
124
- }
125
-
126
- export class ParallelProcessor extends EventEmitter {
127
- private workers = new Map<string, Worker>();
128
- private workerStats = new Map<string, WorkerStats>();
129
- private taskQueue: ParallelTask[] = [];
130
- private queueInterval?: NodeJS.Timeout;
131
- private activeTasks = new Map<string, ParallelTask>();
132
- private pendingResults = new Map<
133
- string,
134
- {
135
- workerId: string;
136
- resolve: (result: ParallelResult<unknown>) => void;
137
- reject: (error: Error) => void;
138
- timeout?: NodeJS.Timeout;
139
- }
140
- >();
141
- private roundRobinIndex = 0;
142
- private options: Required<ParallelProcessorOptions>;
143
- private disposed = false;
144
- private debug = false;
145
-
146
- constructor(options: ParallelProcessorOptions = {}) {
147
- super();
148
-
149
- const defaultWorkerScript = fileURLToPath(import.meta.url);
150
- this.options = {
151
- maxWorkers: options.maxWorkers ?? Math.min(cpus().length, 8),
152
- taskTimeout: options.taskTimeout ?? 30000,
153
- retryAttempts: options.retryAttempts ?? 2,
154
- enableMemoryMonitoring: options.enableMemoryMonitoring ?? true,
155
- enableCaching: options.enableCaching ?? true,
156
- workerScript: options.workerScript ?? defaultWorkerScript,
157
- batchSize: options.batchSize ?? 1,
158
- loadBalancing: options.loadBalancing ?? 'least-loaded',
159
- debug: options.debug ?? false,
160
- };
161
-
162
- this.debug = this.options.debug;
163
-
164
- if (this.debug) {
165
- this.debugLog(`🚀 Parallel processor initialized with ${this.options.maxWorkers} workers`);
166
- }
167
- }
168
-
169
- private debugLog(message: string): void {
170
- if (!this.debug) {
171
- return;
172
- }
173
- process.stdout.write(`${message}\n`);
174
- }
175
-
176
- /**
177
- * Initialize worker pool
178
- */
179
- async init(): Promise<void> {
180
- if (this.disposed) {
181
- throw new Error('Processor has been disposed');
182
- }
183
-
184
- // Spawn initial worker pool
185
- for (let i = 0; i < this.options.maxWorkers; i++) {
186
- await this.spawnWorker(`worker_${i}`);
187
- }
188
-
189
- // Start task processing
190
- this.processQueue();
191
-
192
- this.debugLog(`✅ Worker pool initialized with ${this.workers.size} workers`);
193
- }
194
-
195
- /**
196
- * Process Python module analysis in parallel
197
- */
198
- async analyzeModulesParallel(
199
- sources: Array<{ name: string; content: string; path?: string }>,
200
- options: { chunkSize?: number } = {}
201
- ): Promise<Array<ParallelResult<AnalysisResult>>> {
202
- const chunkSize = options.chunkSize ?? Math.ceil(sources.length / this.options.maxWorkers);
203
- const chunks = this.chunkArray(sources, chunkSize);
204
-
205
- this.debugLog(`📊 Analyzing ${sources.length} modules in ${chunks.length} chunks`);
206
-
207
- const tasks: Array<ParallelTask<AnalyzeTaskData>> = chunks.map((chunk, index) => ({
208
- id: `analyze_chunk_${index}`,
209
- type: 'analyze',
210
- data: { sources: chunk },
211
- priority: 1,
212
- }));
213
-
214
- const results = await this.executeTasks<AnalyzeTaskData, AnalyzeTaskResult>(tasks);
215
-
216
- // Flatten chunked results
217
- const flatResults: ParallelResult<AnalysisResult>[] = [];
218
- for (const result of results) {
219
- if (!result.success || !result.result) {
220
- continue;
221
- }
222
- if (result.success && result.result) {
223
- const chunkResults = result.result;
224
- if (Array.isArray(chunkResults)) {
225
- flatResults.push(...chunkResults);
226
- }
227
- }
228
- }
229
-
230
- return flatResults;
231
- }
232
-
233
- /**
234
- * Generate TypeScript wrappers in parallel
235
- */
236
- async generateWrappersParallel(
237
- modules: Array<{ name: string; module: PythonModule; options?: Record<string, unknown> }>,
238
- options: { chunkSize?: number } = {}
239
- ): Promise<Array<ParallelResult<GeneratedCode>>> {
240
- const chunkSize = options.chunkSize ?? Math.ceil(modules.length / this.options.maxWorkers);
241
- const chunks = this.chunkArray(modules, chunkSize);
242
-
243
- this.debugLog(`🏗️ Generating ${modules.length} wrappers in ${chunks.length} chunks`);
244
-
245
- const tasks: Array<ParallelTask<GenerationTaskData>> = chunks.map((chunk, index) => ({
246
- id: `generate_chunk_${index}`,
247
- type: 'generate',
248
- data: { modules: chunk },
249
- priority: 1,
250
- }));
251
-
252
- const results = await this.executeTasks<GenerationTaskData, GenerateTaskResult>(tasks);
253
-
254
- // Flatten chunked results
255
- const flatResults: ParallelResult<GeneratedCode>[] = [];
256
- for (const result of results) {
257
- if (result.success && result.result) {
258
- const chunkResults = result.result;
259
- if (Array.isArray(chunkResults)) {
260
- flatResults.push(...chunkResults);
261
- }
262
- }
263
- }
264
-
265
- return flatResults;
266
- }
267
-
268
- /**
269
- * Execute tasks in parallel with load balancing
270
- */
271
- async executeTasks<Data, Result>(
272
- tasks: ParallelTask<Data>[]
273
- ): Promise<Array<ParallelResult<Result>>> {
274
- if (this.workers.size === 0) {
275
- await this.init();
276
- }
277
-
278
- // Sort tasks by priority
279
- const sortedTasks = [...tasks].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
280
-
281
- // Execute tasks with batching
282
- const results: Array<ParallelResult<Result>> = [];
283
- const inFlight = new Set<Promise<ParallelResult<Result>>>();
284
- const maxInFlight = this.workers.size * 2;
285
-
286
- for (const task of sortedTasks) {
287
- const promise = this.executeTask<Data, Result>(task);
288
- inFlight.add(promise);
289
- promise
290
- .finally(() => {
291
- inFlight.delete(promise);
292
- })
293
- .catch(() => {
294
- // no-op: individual task failures are encoded in results
295
- });
296
-
297
- // Limit concurrent tasks to prevent overwhelming workers
298
- if (inFlight.size >= maxInFlight) {
299
- const completed = await Promise.race(inFlight);
300
- results.push(completed);
301
- }
302
- }
303
-
304
- // Wait for remaining tasks
305
- const remaining = await Promise.all(inFlight);
306
- results.push(...remaining);
307
-
308
- return results;
309
- }
310
-
311
- /**
312
- * Execute single task
313
- */
314
- private async executeTask<Data, Result>(
315
- task: ParallelTask<Data>
316
- ): Promise<ParallelResult<Result>> {
317
- // Check cache first if enabled
318
- if (this.options.enableCaching) {
319
- const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
320
- const cached = await globalCache.get<ParallelResult<Result>>(cacheKey);
321
- if (cached) {
322
- this.debugLog(`🎯 Cache HIT for task ${task.id}`);
323
- return cached;
324
- }
325
- }
326
-
327
- let attempts = 0;
328
- let lastError: Error | undefined;
329
-
330
- while (attempts < this.options.retryAttempts) {
331
- attempts++;
332
-
333
- try {
334
- const selected = this.selectOptimalWorker();
335
- if (!selected) {
336
- throw new Error('No available workers');
337
- }
338
-
339
- const result = await this.sendTaskToWorker<Data, Result>(
340
- selected.workerId,
341
- selected.worker,
342
- task
343
- );
344
-
345
- // Cache successful results
346
- if (this.options.enableCaching && result.success) {
347
- const cacheKey = globalCache.generateKey('parallel_task', task.type, task.data);
348
- await globalCache.set(cacheKey, result, { computeTime: result.duration });
349
- }
350
-
351
- return result;
352
- } catch (error) {
353
- lastError = error as Error;
354
- log.warn('Task failed', { taskId: task.id, attempt: attempts, error: String(error) });
355
-
356
- if (attempts < this.options.retryAttempts) {
357
- // Exponential backoff
358
- await this.sleep(Math.pow(2, attempts) * 1000);
359
- }
360
- }
361
- }
362
-
363
- // Return failure result
364
- return {
365
- taskId: task.id,
366
- success: false,
367
- error: lastError?.message ?? 'Task failed after retries',
368
- duration: 0,
369
- };
370
- }
371
-
372
- /**
373
- * Select optimal worker using load balancing strategy
374
- */
375
- private selectOptimalWorker(): { workerId: string; worker: Worker } | null {
376
- const availableWorkers = Array.from(this.workers.entries())
377
- .map(([workerId, worker]) => ({ workerId, worker }))
378
- .filter(({ workerId, worker }) => {
379
- if (worker.threadId <= 0) {
380
- return false;
381
- }
382
- const stats = this.workerStats.get(workerId);
383
- return stats?.isActive !== false;
384
- });
385
-
386
- if (availableWorkers.length === 0) {
387
- return null;
388
- }
389
-
390
- switch (this.options.loadBalancing) {
391
- case 'round-robin': {
392
- const selected = availableWorkers[this.roundRobinIndex % availableWorkers.length];
393
- this.roundRobinIndex = (this.roundRobinIndex + 1) % availableWorkers.length;
394
- return selected ?? null;
395
- }
396
-
397
- case 'least-loaded': {
398
- // Find worker with least active tasks
399
- const workerLoads = availableWorkers.map(({ workerId, worker }) => {
400
- const stats = this.workerStats.get(workerId);
401
- return { workerId, worker, load: stats?.tasksCompleted ?? 0 };
402
- });
403
-
404
- workerLoads.sort((a, b) => a.load - b.load);
405
- const first = workerLoads[0];
406
- return first ? { workerId: first.workerId, worker: first.worker } : null;
407
- }
408
-
409
- case 'weighted': {
410
- // Weight by average task completion time
411
- const workerWeights = availableWorkers.map(({ workerId, worker }) => {
412
- const stats = this.workerStats.get(workerId);
413
- const avgTime = stats?.averageTime ?? 1000;
414
- return { workerId, worker, weight: 1 / avgTime };
415
- });
416
-
417
- // Weighted random selection
418
- const totalWeight = workerWeights.reduce((sum, w) => sum + w.weight, 0);
419
- let random = Math.random() * totalWeight;
420
-
421
- for (const { workerId, worker: candidate, weight } of workerWeights) {
422
- random -= weight;
423
- if (random <= 0) {
424
- return { workerId, worker: candidate };
425
- }
426
- }
427
-
428
- const fallback = workerWeights[0];
429
- return fallback ? { workerId: fallback.workerId, worker: fallback.worker } : null;
430
- }
431
-
432
- default:
433
- return availableWorkers[0] ?? null;
434
- }
435
- }
436
-
437
- /**
438
- * Send task to specific worker
439
- */
440
- private async sendTaskToWorker<Data, Result>(
441
- workerId: string,
442
- worker: Worker,
443
- task: ParallelTask<Data>
444
- ): Promise<ParallelResult<Result>> {
445
- return new Promise((resolve, reject) => {
446
- if (this.disposed) {
447
- reject(new Error('Processor has been disposed'));
448
- return;
449
- }
450
-
451
- const timeoutMs = task.timeout ?? this.options.taskTimeout;
452
- const timeout = setTimeout(() => {
453
- const pending = this.pendingResults.get(task.id);
454
- if (!pending) {
455
- return;
456
- }
457
- this.pendingResults.delete(task.id);
458
- this.activeTasks.delete(task.id);
459
- pending.reject(new Error(`Task ${task.id} timed out after ${timeoutMs}ms`));
460
- }, timeoutMs);
461
-
462
- this.pendingResults.set(task.id, {
463
- workerId,
464
- resolve: resolve as unknown as (result: ParallelResult<unknown>) => void,
465
- reject,
466
- timeout,
467
- });
468
-
469
- this.activeTasks.set(task.id, task);
470
-
471
- try {
472
- worker.postMessage({
473
- type: 'task',
474
- task,
475
- });
476
- } catch (error: unknown) {
477
- this.pendingResults.delete(task.id);
478
- this.activeTasks.delete(task.id);
479
- clearTimeout(timeout);
480
- reject(error instanceof Error ? error : new Error(String(error)));
481
- }
482
- });
483
- }
484
-
485
- /**
486
- * Spawn new worker
487
- */
488
- private async spawnWorker(workerId: string): Promise<Worker> {
489
- const worker = new Worker(this.options.workerScript, {
490
- workerData: {
491
- workerId,
492
- options: {
493
- enableMemoryMonitoring: this.options.enableMemoryMonitoring,
494
- enableCaching: this.options.enableCaching,
495
- },
496
- },
497
- });
498
-
499
- // Setup worker message handling
500
- worker.on('message', message => {
501
- this.handleWorkerMessage(workerId, message);
502
- });
503
-
504
- // Setup worker error handling
505
- worker.on('error', error => {
506
- const workerError = error instanceof Error ? error : new Error(String(error));
507
- log.error('Worker error', { workerId, error: String(workerError) });
508
- this.handleWorkerError(workerId, workerError);
509
- });
510
-
511
- // Setup worker exit handling
512
- worker.on('exit', code => {
513
- log.warn('Worker exited', { workerId, code });
514
- this.handleWorkerExit(workerId, code);
515
- });
516
-
517
- this.workers.set(workerId, worker);
518
- this.workerStats.set(workerId, {
519
- workerId,
520
- tasksCompleted: 0,
521
- totalTime: 0,
522
- averageTime: 0,
523
- errorCount: 0,
524
- memoryPeak: 0,
525
- isActive: true,
526
- });
527
-
528
- this.debugLog(`👷 Spawned worker ${workerId}`);
529
- return worker;
530
- }
531
-
532
- /**
533
- * Handle message from worker
534
- */
535
- private handleWorkerMessage(workerId: string, message: WorkerMessage): void {
536
- if (message.type === 'task_complete') {
537
- const { taskId, result, error, duration, memoryUsage } = message;
538
- const pending = this.pendingResults.get(taskId);
539
- if (pending) {
540
- this.pendingResults.delete(taskId);
541
- this.activeTasks.delete(taskId);
542
-
543
- if (pending.timeout) {
544
- clearTimeout(pending.timeout);
545
- }
546
-
547
- // Update worker stats
548
- const stats = this.workerStats.get(workerId);
549
- if (stats) {
550
- stats.tasksCompleted++;
551
- stats.totalTime += duration;
552
- stats.averageTime = stats.totalTime / stats.tasksCompleted;
553
-
554
- if (error) {
555
- stats.errorCount++;
556
- }
557
-
558
- if (memoryUsage && memoryUsage > stats.memoryPeak) {
559
- stats.memoryPeak = memoryUsage;
560
- }
561
- }
562
-
563
- const parallelResult: ParallelResult = {
564
- taskId,
565
- success: !error,
566
- result,
567
- error,
568
- duration,
569
- memoryUsage,
570
- };
571
-
572
- pending.resolve(parallelResult);
573
- }
574
- } else if (message.type === 'worker_ready') {
575
- this.emit('worker_ready', workerId);
576
- }
577
- }
578
-
579
- /**
580
- * Handle worker error
581
- */
582
- private handleWorkerError(workerId: string, error: Error): void {
583
- const stats = this.workerStats.get(workerId);
584
- if (stats) {
585
- stats.errorCount++;
586
- stats.isActive = false;
587
- }
588
-
589
- // Reject only tasks owned by this worker.
590
- const ownedTasks: string[] = [];
591
- for (const [taskId, pending] of this.pendingResults) {
592
- if (pending.workerId === workerId) {
593
- ownedTasks.push(taskId);
594
- }
595
- }
596
- for (const taskId of ownedTasks) {
597
- const pending = this.pendingResults.get(taskId);
598
- if (!pending) {
599
- continue;
600
- }
601
- if (pending.timeout) {
602
- clearTimeout(pending.timeout);
603
- }
604
- pending.reject(new Error(`Worker ${workerId} error: ${error.message}`));
605
- this.pendingResults.delete(taskId);
606
- this.activeTasks.delete(taskId);
607
- }
608
-
609
- this.emit('worker_error', workerId, error);
610
- }
611
-
612
- /**
613
- * Handle worker exit
614
- */
615
- private handleWorkerExit(workerId: string, code: number): void {
616
- this.workers.delete(workerId);
617
-
618
- const stats = this.workerStats.get(workerId);
619
- if (stats) {
620
- stats.isActive = false;
621
- }
622
-
623
- // Reject any tasks that were in-flight on this worker.
624
- const ownedTasks: string[] = [];
625
- for (const [taskId, pending] of this.pendingResults) {
626
- if (pending.workerId === workerId) {
627
- ownedTasks.push(taskId);
628
- }
629
- }
630
- for (const taskId of ownedTasks) {
631
- const pending = this.pendingResults.get(taskId);
632
- if (!pending) {
633
- continue;
634
- }
635
- if (pending.timeout) {
636
- clearTimeout(pending.timeout);
637
- }
638
- pending.reject(new Error(`Worker ${workerId} exited with code ${code}`));
639
- this.pendingResults.delete(taskId);
640
- this.activeTasks.delete(taskId);
641
- }
642
-
643
- // Respawn worker if not disposing
644
- if (!this.disposed && this.workers.size < this.options.maxWorkers) {
645
- this.spawnWorker(`${workerId}_respawn_${Date.now()}`).catch(error => {
646
- log.error('Failed to respawn worker', { workerId, error: String(error) });
647
- });
648
- }
649
-
650
- this.emit('worker_exit', workerId, code);
651
- }
652
-
653
- /**
654
- * Process task queue
655
- */
656
- private processQueue(): void {
657
- // Simple queue processing - could be enhanced with priority scheduling
658
- if (this.queueInterval) {
659
- return;
660
- }
661
- this.queueInterval = setInterval(() => {
662
- if (this.taskQueue.length > 0 && this.workers.size > 0) {
663
- const task = this.taskQueue.shift();
664
- if (task) {
665
- this.executeTask(task).catch(error => {
666
- log.error('Queue task execution failed', { error: String(error) });
667
- });
668
- }
669
- }
670
- }, 100);
671
- // Don't keep the event loop alive just for an idle queue pump.
672
- this.queueInterval.unref?.();
673
- }
674
-
675
- /**
676
- * Get worker statistics
677
- */
678
- getWorkerStats(): WorkerStats[] {
679
- return Array.from(this.workerStats.values());
680
- }
681
-
682
- /**
683
- * Get overall processing statistics
684
- */
685
- getStats(): ParallelProcessorStats {
686
- const workers = Array.from(this.workerStats.values());
687
- const totalTasks = workers.reduce((sum, w) => sum + w.tasksCompleted, 0);
688
- const totalErrors = workers.reduce((sum, w) => sum + w.errorCount, 0);
689
- const totalTime = workers.reduce((sum, w) => sum + w.totalTime, 0);
690
- const avgTime = totalTasks > 0 ? totalTime / totalTasks : 0;
691
-
692
- return {
693
- activeWorkers: workers.filter(w => w.isActive).length,
694
- totalWorkers: workers.length,
695
- tasksCompleted: totalTasks,
696
- totalErrors,
697
- averageTaskTime: avgTime,
698
- queueLength: this.taskQueue.length,
699
- activeTasks: this.activeTasks.size,
700
- workerStats: workers,
701
- };
702
- }
703
-
704
- /**
705
- * Utility methods
706
- */
707
- private chunkArray<T>(array: T[], chunkSize: number): T[][] {
708
- const chunks: T[][] = [];
709
- for (let i = 0; i < array.length; i += chunkSize) {
710
- chunks.push(array.slice(i, i + chunkSize));
711
- }
712
- return chunks;
713
- }
714
-
715
- private sleep(ms: number): Promise<void> {
716
- return new Promise(resolve => setTimeout(resolve, ms));
717
- }
718
-
719
- /**
720
- * Gracefully dispose all workers
721
- */
722
- async dispose(): Promise<void> {
723
- if (this.disposed) {
724
- return;
725
- }
726
-
727
- this.disposed = true;
728
-
729
- this.debugLog('🛑 Disposing parallel processor...');
730
-
731
- if (this.queueInterval) {
732
- clearInterval(this.queueInterval);
733
- this.queueInterval = undefined;
734
- }
735
-
736
- // Reject any pending results immediately so callers don't hang.
737
- const disposedError = new Error('Processor has been disposed');
738
- for (const pending of this.pendingResults.values()) {
739
- if (pending.timeout) {
740
- clearTimeout(pending.timeout);
741
- }
742
- pending.reject(disposedError);
743
- }
744
- this.pendingResults.clear();
745
- this.activeTasks.clear();
746
-
747
- // Terminate all workers
748
- const terminationPromises = Array.from(this.workers.values()).map(worker => {
749
- return new Promise<void>(resolve => {
750
- const timeout = setTimeout(() => {
751
- worker.terminate();
752
- resolve();
753
- }, 5000);
754
-
755
- worker.once('exit', () => {
756
- clearTimeout(timeout);
757
- resolve();
758
- });
759
-
760
- try {
761
- worker.postMessage({ type: 'shutdown' });
762
- } catch {
763
- // Ignore: worker may already be dead.
764
- }
765
- });
766
- });
767
-
768
- await Promise.all(terminationPromises);
769
-
770
- // Clear data structures
771
- this.workers.clear();
772
- this.workerStats.clear();
773
- this.taskQueue.length = 0;
774
-
775
- this.removeAllListeners();
776
-
777
- this.debugLog('✅ Parallel processor disposed');
778
- }
779
-
780
- setDebug(debug: boolean): void {
781
- this.debug = debug;
782
- }
783
- }
784
-
785
- // Worker script implementation (this runs in worker threads)
786
- if (!isMainThread && parentPort) {
787
- const port = parentPort;
788
- const { workerId } = workerData as WorkerData;
789
-
790
- let analyzer: PyAnalyzer | null = null;
791
- let analyzerInitialized = false;
792
- let generator: CodeGenerator | null = null;
793
-
794
- const getAnalyzer = async (): Promise<PyAnalyzer> => {
795
- analyzer ??= new PyAnalyzer();
796
- if (!analyzerInitialized) {
797
- await analyzer.initialize();
798
- analyzerInitialized = true;
799
- }
800
- return analyzer;
801
- };
802
-
803
- const getGenerator = (): CodeGenerator => {
804
- generator ??= new CodeGenerator();
805
- return generator;
806
- };
807
-
808
- const createResultId = (prefix: string, name: string, index: number): string =>
809
- `${prefix}:${name}:${index}`;
810
-
811
- // Import required modules in worker context
812
- const workerImplementation = {
813
- async processTask(task: WorkerTask): Promise<WorkerTaskResult> {
814
- const startTime = performance.now();
815
- let result: unknown;
816
- let error: string | undefined;
817
-
818
- try {
819
- switch (task.type) {
820
- case 'analyze':
821
- result = await this.processAnalysisTask(task.data);
822
- break;
823
- case 'generate':
824
- result = await this.processGenerationTask(task.data);
825
- break;
826
- case 'validate':
827
- result = await this.processValidationTask(task.data);
828
- break;
829
- default:
830
- throw new Error(`Unknown task type: ${task.type}`);
831
- }
832
- } catch (err) {
833
- error = String(err);
834
- }
835
-
836
- const duration = performance.now() - startTime;
837
- const memoryUsage = process.memoryUsage().heapUsed;
838
-
839
- return { result, error, duration, memoryUsage };
840
- },
841
-
842
- async processAnalysisTask(data: AnalyzeTaskData): Promise<AnalyzeTaskResult> {
843
- const results: AnalyzeTaskResult = [];
844
- const activeAnalyzer = await getAnalyzer();
845
-
846
- for (const [index, source] of data.sources.entries()) {
847
- const start = performance.now();
848
- try {
849
- const modulePath = source.path ?? `${source.name}.py`;
850
- const analysis = await activeAnalyzer.analyzePythonModule(source.content, modulePath);
851
- results.push({
852
- taskId: createResultId('analyze', source.name, index),
853
- success: true,
854
- result: analysis,
855
- duration: performance.now() - start,
856
- memoryUsage: process.memoryUsage().heapUsed,
857
- });
858
- } catch (error) {
859
- results.push({
860
- taskId: createResultId('analyze', source.name, index),
861
- success: false,
862
- error: String(error),
863
- duration: performance.now() - start,
864
- memoryUsage: process.memoryUsage().heapUsed,
865
- });
866
- }
867
- }
868
-
869
- return results;
870
- },
871
-
872
- async processGenerationTask(data: GenerationTaskData): Promise<GenerateTaskResult> {
873
- const results: GenerateTaskResult = [];
874
- const activeGenerator = getGenerator();
875
-
876
- for (const [index, moduleData] of data.modules.entries()) {
877
- const start = performance.now();
878
- try {
879
- const generationOptions = {
880
- moduleName: moduleData.name,
881
- ...moduleData.options,
882
- } as {
883
- moduleName: string;
884
- exportAll?: boolean;
885
- annotatedJSDoc?: boolean;
886
- };
887
- const generated = await activeGenerator.generateModule(
888
- moduleData.module,
889
- generationOptions
890
- );
891
- results.push({
892
- taskId: createResultId('generate', moduleData.name, index),
893
- success: true,
894
- result: generated,
895
- duration: performance.now() - start,
896
- memoryUsage: process.memoryUsage().heapUsed,
897
- });
898
- } catch (error) {
899
- results.push({
900
- taskId: createResultId('generate', moduleData.name, index),
901
- success: false,
902
- error: String(error),
903
- duration: performance.now() - start,
904
- memoryUsage: process.memoryUsage().heapUsed,
905
- });
906
- }
907
- }
908
-
909
- return results;
910
- },
911
-
912
- async processValidationTask(_data: ValidationTaskData): Promise<ValidateTaskResult> {
913
- // Validation logic would go here
914
- return { validated: true };
915
- },
916
- };
917
-
918
- port.on('message', async (raw: unknown) => {
919
- const message = raw as WorkerControlMessage;
920
- if (!message || typeof message !== 'object') {
921
- return;
922
- }
923
-
924
- if (message.type === 'task') {
925
- try {
926
- const taskResult = await workerImplementation.processTask(message.task);
927
-
928
- port.postMessage({
929
- type: 'task_complete',
930
- taskId: message.task.id,
931
- ...(typeof taskResult === 'object' && taskResult !== null
932
- ? taskResult
933
- : { result: taskResult }),
934
- });
935
- } catch (error) {
936
- port.postMessage({
937
- type: 'task_complete',
938
- taskId: message.task.id,
939
- error: String(error),
940
- duration: 0,
941
- });
942
- }
943
- } else if (message.type === 'shutdown') {
944
- process.exit(0);
945
- }
946
- });
947
-
948
- // Signal that worker is ready
949
- port.postMessage({
950
- type: 'worker_ready',
951
- workerId,
952
- });
953
- }
954
-
955
- export const globalParallelProcessor = new ParallelProcessor();