versacompiler 2.1.0 → 2.3.0
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 +1 -1
- package/dist/compiler/compile.js +2520 -25
- package/dist/compiler/error-reporter.js +467 -38
- package/dist/compiler/linter.js +72 -1
- package/dist/compiler/minify.js +272 -1
- package/dist/compiler/minifyTemplate.js +230 -1
- package/dist/compiler/module-resolution-optimizer.js +888 -1
- package/dist/compiler/parser.js +336 -1
- package/dist/compiler/performance-monitor.js +204 -56
- package/dist/compiler/tailwindcss.js +39 -1
- package/dist/compiler/transform-optimizer.js +392 -1
- package/dist/compiler/transformTStoJS.js +16 -1
- package/dist/compiler/transforms.js +554 -1
- package/dist/compiler/typescript-compiler.js +172 -2
- package/dist/compiler/typescript-error-parser.js +281 -10
- package/dist/compiler/typescript-manager.js +304 -2
- package/dist/compiler/typescript-sync-validator.js +295 -31
- package/dist/compiler/typescript-worker-pool.js +936 -1
- package/dist/compiler/typescript-worker-thread.cjs +466 -22
- package/dist/compiler/typescript-worker.js +339 -1
- package/dist/compiler/vuejs.js +396 -37
- package/dist/hrm/VueHRM.js +359 -1
- package/dist/hrm/errorScreen.js +83 -1
- package/dist/hrm/getInstanciaVue.js +313 -1
- package/dist/hrm/initHRM.js +586 -1
- package/dist/main.js +353 -7
- package/dist/servicios/browserSync.js +589 -2
- package/dist/servicios/file-watcher.js +425 -4
- package/dist/servicios/logger.js +63 -3
- package/dist/servicios/readConfig.js +399 -53
- package/dist/utils/excluded-modules.js +37 -1
- package/dist/utils/module-resolver.js +552 -1
- package/dist/utils/promptUser.js +48 -2
- package/dist/utils/proxyValidator.js +68 -1
- package/dist/utils/resolve-bin.js +58 -1
- package/dist/utils/utils.js +21 -1
- package/dist/utils/vue-types-setup.js +435 -241
- package/dist/wrappers/eslint-node.js +1 -1
- package/dist/wrappers/oxlint-node.js +122 -1
- package/dist/wrappers/tailwind-node.js +94 -1
- package/package.json +109 -104
|
@@ -1 +1,936 @@
|
|
|
1
|
-
import*as e from"node:os";import*as t from"node:path";import*as n from"node:process";import{Worker as r}from"node:worker_threads";import{validateTypesWithLanguageService as i}from"./typescript-sync-validator.js";export class TypeScriptWorkerPool{static instance;workers=[];poolSize;workerPath;initPromise=null;isInitialized=!1;TASK_TIMEOUT=8e3;WORKER_INIT_TIMEOUT=3e3;MAX_TASKS_PER_WORKER=200;WORKER_MEMORY_CHECK_INTERVAL=500;memoryCheckInterval=null;cleanupInterval=null;totalTasks=0;completedTasks=0;failedTasks=0;constructor(){let r=e.cpus().length;this.poolSize=Math.min(Math.max(r,4),16),this.workerPath=t.join(n.env.PATH_PROY||t.join(n.cwd(),`src`),`compiler`,`typescript-worker-thread.cjs`)}startMemoryMonitoring(){this.stopMemoryMonitoring(),this.memoryCheckInterval=setInterval(()=>{this.checkWorkersMemory()},15e3),this.cleanupInterval=setInterval(()=>{this.cleanupInactiveWorkers()},12e4)}stopMemoryMonitoring(){this.memoryCheckInterval&&(clearInterval(this.memoryCheckInterval),this.memoryCheckInterval=null),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null)}async checkWorkersMemory(){let e=Date.now();for(let t of this.workers){t.lastMemoryCheck=e;try{if(t.memoryUsage=(await this.getWorkerMemoryUsage(t)).heapUsed,this.shouldRecycleWorker(t)){let e=this.getRecycleReason(t);console.warn(`[WorkerPool] Worker ${t.id} requiere reciclaje: ${e}`),await this.recycleWorker(t)}}catch(e){console.warn(`[WorkerPool] Error verificando memoria del worker ${t.id}:`,e)}}}async getWorkerMemoryUsage(e){return new Promise(t=>{let n=setTimeout(()=>{t({heapUsed:e.tasksProcessed*2048,heapTotal:e.tasksProcessed*3072,rss:e.tasksProcessed*4096})},1e3),r=`memory-${e.id}-${Date.now()}`,i=a=>{a.id===r&&a.type===`memory-usage`&&(clearTimeout(n),e.worker.off(`message`,i),t({heapUsed:a.memoryUsage?.heapUsed||e.tasksProcessed*2048,heapTotal:a.memoryUsage?.heapTotal||e.tasksProcessed*3072,rss:a.memoryUsage?.rss||e.tasksProcessed*4096}))};e.worker.on(`message`,i),e.worker.postMessage({type:`get-memory-usage`,id:r})})}getRecycleReason(e){let t=Date.now(),n=100*1024*1024,r=1800*1e3,i=this.MAX_TASKS_PER_WORKER,a=[];return e.memoryUsage>n&&a.push(`memoria excede ${Math.round(n/1024/1024)}MB (actual: ${Math.round(e.memoryUsage/1024/1024)}MB)`),t-e.creationTime>r&&a.push(`tiempo de vida excede ${Math.round(r/6e4)} minutos`),e.tasksProcessed>=i&&a.push(`tareas procesadas exceden ${i} (actual: ${e.tasksProcessed})`),a.join(`, `)}async cleanupInactiveWorkers(){let e=Date.now();for(let t of this.workers){let n=e-t.lastActivityTime;n>3e5&&!t.busy&&t.pendingTasks.size===0&&(console.info(`[WorkerPool] Worker ${t.id} inactivo por ${Math.round(n/6e4)} minutos, reciclando...`),await this.recycleWorker(t))}}shouldRecycleWorker(e){let t=Date.now(),n=this.MAX_TASKS_PER_WORKER;return e.memoryUsage>104857600||t-e.creationTime>18e5||e.tasksProcessed>=n}static getInstance(){return TypeScriptWorkerPool.instance||(TypeScriptWorkerPool.instance=new TypeScriptWorkerPool),TypeScriptWorkerPool.instance}setMode(t){switch(t){case`batch`:this.poolSize=Math.min(e.cpus().length,20);break;case`watch`:this.poolSize=Math.min(e.cpus().length,12);break;case`individual`:this.poolSize=Math.min(8,e.cpus().length);break}}async initializePool(){return this.initPromise||(this.initPromise=this._performPoolInitialization()),this.initPromise}async _performPoolInitialization(){try{if(!(await import(`node:fs`)).existsSync(this.workerPath))throw Error(`Worker thread file not found: ${this.workerPath}`);let e=Array.from({length:this.poolSize},(e,t)=>this.createWorker(t));this.workers=await Promise.all(e),this.isInitialized=!0}catch(e){throw this.isInitialized=!1,e}}async createWorker(e){return new Promise((t,i)=>{try{let a=new r(this.workerPath,{env:{...n.env,NODE_OPTIONS:``,WORKER_ID:e.toString()}}),o={worker:a,id:e,busy:!1,pendingTasks:new Map,taskCounter:0,memoryUsage:0,lastMemoryCheck:Date.now(),tasksProcessed:0,creationTime:Date.now(),lastActivityTime:Date.now()};this.setupWorkerListeners(o);let s=setTimeout(()=>{i(Error(`Worker ${e} initialization timeout`))},this.WORKER_INIT_TIMEOUT);a.on(`message`,function e(n){(n.id===`worker-ready`||n.message===`pong`)&&(clearTimeout(s),a.off(`message`,e),t(o))}),a.on(`error`,e=>{clearTimeout(s),i(e)}),a.postMessage({type:`ping`})}catch(e){i(e)}})}setupWorkerListeners(e){let{worker:t}=e;t.on(`message`,t=>{try{if(t.id===`worker-ready`||t.message===`pong`||t.type===`memory-usage`)return;let n=e.pendingTasks.get(t.id);if(!n)return;if(clearTimeout(n.timeout),e.pendingTasks.delete(t.id),e.pendingTasks.size===0&&(e.busy=!1),e.lastActivityTime=Date.now(),t.success&&t.diagnostics!==void 0&&t.hasErrors!==void 0){this.completedTasks++;let e=this.categorizeTypeScriptErrors({diagnostics:t.diagnostics,hasErrors:t.hasErrors},n.fileName);n.resolve(e)}else{this.failedTasks++;let e=t.error||`Error desconocido del worker`,r=this.createCategorizedError(e,n.fileName,t);n.reject(r)}}catch{}}),t.on(`error`,async t=>{await this.handleWorkerError(e,t)}),t.on(`exit`,async t=>{await this.handleWorkerExit(e,t)})}async handleWorkerError(e,t){console.warn(`[WorkerPool] Manejando error del worker ${e.id}:`,t.message),e.pendingTasks.forEach(n=>{clearTimeout(n.timeout),this.failedTasks++,n.reject(Error(`Worker ${e.id} failed: ${t.message}`))}),e.pendingTasks.clear();try{e.worker.removeAllListeners(),await e.worker.terminate()}catch(t){console.error(`[WorkerPool] Error terminando worker ${e.id}:`,t)}if(e.busy=!1,this.isInitialized&&this.workers.length>0)try{let t=await this.createWorker(e.id),n=this.workers.findIndex(t=>t.id===e.id);n!==-1&&(this.workers[n]=t)}catch(t){console.error(`[WorkerPool] No se pudo recrear worker ${e.id}:`,t)}}async handleWorkerExit(e,t){console.warn(`[WorkerPool] Worker ${e.id} salió con código ${t}`),e.pendingTasks.forEach(n=>{clearTimeout(n.timeout),this.failedTasks++,n.reject(Error(`Worker ${e.id} exited with code ${t}`))}),e.pendingTasks.clear(),e.busy=!1;try{e.worker.removeAllListeners()}catch{}if(this.isInitialized&&this.workers.length>0)try{await this.recreateWorker(e)}catch(t){console.error(`[WorkerPool] Error recreando worker ${e.id}:`,t)}}async recreateWorker(e){try{let t=await this.createWorker(e.id),n=this.workers.findIndex(t=>t.id===e.id);n!==-1&&(this.workers[n]=t)}catch{}}async recycleWorker(e){try{console.log(`[WorkerPool] Reciclando worker ${e.id} después de ${e.taskCounter} tareas`);let t=Date.now();await new Promise(n=>{let r=()=>{e.pendingTasks.size===0||Date.now()-t>=2e3?n():setTimeout(r,100)};r()});let n=Array.from(e.pendingTasks.entries());for(let[e,t]of n)clearTimeout(t.timeout),t.reject(Error(`Worker being recycled`));e.pendingTasks.clear();let r=e.worker;r.removeAllListeners(`message`),r.removeAllListeners(`error`),r.removeAllListeners(`exit`),r.removeAllListeners(`online`),r.removeAllListeners(`messageerror`);let i=e.worker;e.worker=null;let a=i.terminate(),o=new Promise((e,t)=>setTimeout(()=>t(Error(`Terminate timeout`)),3e3));await Promise.race([a,o]).catch(t=>{console.warn(`[WorkerPool] Warning al terminar worker ${e.id}:`,t)});let s=await this.createWorker(e.id),c=this.workers.findIndex(t=>t.id===e.id);c!==-1&&(this.workers[c]=s)}catch(t){console.error(`[WorkerPool] Error reciclando worker ${e.id}:`,t)}}findAvailableWorker(){let e=this.workers.find(e=>!e.busy&&e.pendingTasks.size===0);if(e)return e;let t=this.workers.reduce((e,t)=>t.pendingTasks.size<e.pendingTasks.size?t:e);return t.pendingTasks.size<5?t:null}async typeCheck(e,t,n){if(await this.initializePool(),!this.isInitialized)return this.typeCheckWithSyncFallback(e,t,n);let r=this.findAvailableWorker();if(!r)return this.typeCheckWithSyncFallback(e,t,n);try{return this.totalTasks++,await this.typeCheckWithWorker(r,e,t,n)}catch{return this.typeCheckWithSyncFallback(e,t,n)}}async typeCheckWithWorker(e,t,n,r){return e.taskCounter>=this.MAX_TASKS_PER_WORKER&&await this.recycleWorker(e),new Promise((i,a)=>{let o=`task-${e.id}-${++e.taskCounter}-${Date.now()}`;e.busy=!0,e.lastActivityTime=Date.now(),e.tasksProcessed++;let s=this.calculateDynamicTimeout(t,n,r),c=setTimeout(()=>{e.pendingTasks.delete(o),e.pendingTasks.size===0&&(e.busy=!1),a(Error(`Timeout (${s}ms) en type checking para ${t} - archivo complejo detectado`))},s);e.pendingTasks.set(o,{resolve:i,reject:a,timeout:c,fileName:t,startTime:Date.now()});try{let i={id:o,fileName:t,content:n,compilerOptions:{...r,_workerTimeout:s-1e3}};e.worker.postMessage(i)}catch(t){clearTimeout(c),e.pendingTasks.delete(o),e.pendingTasks.size===0&&(e.busy=!1),a(t)}})}calculateDynamicTimeout(e,t,n){let r=this.TASK_TIMEOUT,i=1,a=t.length;a>1e5?i+=1.5:a>5e4?i+=1:a>2e4&&(i+=.5);let o=(t.match(/^import\s+/gm)||[]).length,s=(t.match(/\btype\s+\w+/g)||[]).length,c=(t.match(/\binterface\s+\w+/g)||[]).length,l=(t.match(/<[^>]*>/g)||[]).length,u=o+s+c+l*.5;u>100?i+=2:u>50?i+=1:u>20&&(i+=.5),(n?.strict||n?.noImplicitAny)&&(i+=.3),e.includes(`.d.ts`)?i+=1:e.includes(`.vue`)&&(i+=.5),i=Math.min(i,5),i=Math.max(i,.5);let d=Math.round(r*i);return Math.min(d,6e4)}typeCheckWithSyncFallback(e,t,n){try{return i(e,t,n)}catch{return{diagnostics:[],hasErrors:!1}}}async terminate(){console.log(`[WorkerPool] Cerrando pool de workers...`),this.stopMemoryMonitoring();let e=0;for(let t of this.workers){e+=t.pendingTasks.size,t.pendingTasks.forEach(e=>{clearTimeout(e.timeout),e.reject(Error(`Worker pool cerrado`))}),t.pendingTasks.clear();try{t.worker.removeAllListeners()}catch{}}e>0&&console.log(`[WorkerPool] Se cancelaron ${e} tareas pendientes`);let t=this.workers.map(async e=>{try{await e.worker.terminate()}catch(t){console.warn(`[WorkerPool] Error terminando worker ${e.id}:`,t)}});await Promise.allSettled(t),this.workers=[],this.isInitialized=!1,this.initPromise=null,console.log(`[WorkerPool] Pool cerrado. Estadísticas finales: ${this.completedTasks} completadas, ${this.failedTasks} fallidas`)}getStats(){let e=this.workers.filter(e=>e.busy).length,t=this.workers.reduce((e,t)=>e+t.pendingTasks.size,0),n=this.totalTasks>0?Math.round(this.completedTasks/this.totalTasks*100):0;return{poolSize:this.workers.length,busyWorkers:e,totalPendingTasks:t,totalTasks:this.totalTasks,completedTasks:this.completedTasks,failedTasks:this.failedTasks,successRate:n}}categorizeTypeScriptErrors(e,t){if(!e.hasErrors||!e.diagnostics?.length)return e;let n=e.diagnostics.map(e=>({...e,_category:this.getErrorCategory(e),_severity:this.getErrorSeverity(e),_fileName:t,_timestamp:Date.now()}));return{...e,diagnostics:n,_errorStats:this.calculateErrorStats(n)}}getErrorCategory(e){let t=e.code;return[2304,2339,2346].includes(t)?`MISSING_DECLARATION`:[2322,2322,2345].includes(t)?`TYPE_MISMATCH`:[2307,2317].includes(t)?`MODULE_RESOLUTION`:[2552,2551].includes(t)?`PROPERTY_ACCESS`:t>=1e3&&t<2e3?`SYNTAX_ERROR`:t>=2e3&&t<3e3?`SEMANTIC_ERROR`:t>=4e3?`CONFIG_ERROR`:`OTHER`}getErrorSeverity(e){switch(e.category){case 1:return`ERROR`;case 2:return`WARNING`;case 3:return`INFO`;default:return`ERROR`}}calculateErrorStats(e){let t={totalErrors:e.length,errorsByCategory:{},errorsBySeverity:{},mostCommonError:null};e.forEach(e=>{let n=e._category||`OTHER`,r=e._severity||`ERROR`;t.errorsByCategory[n]=(t.errorsByCategory[n]||0)+1,t.errorsBySeverity[r]=(t.errorsBySeverity[r]||0)+1});let n={};e.forEach(e=>{let t=String(e.code);n[t]=(n[t]||0)+1});let r=Object.keys(n);if(r.length>0){let i=r.reduce((e,t)=>(n[e]||0)>(n[t]||0)?e:t);t.mostCommonError={code:i,count:n[i],message:e.find(e=>String(e.code)===i)?.messageText}}return t}createCategorizedError(e,t,n){let r=Error(e);return r.fileName=t,r.timestamp=Date.now(),r.workerResponse=n,r.category=this.categorizeGenericError(e),r.isRecoverable=this.isRecoverableError(e),r}categorizeGenericError(e){return e.includes(`timeout`)||e.includes(`Timeout`)?`TIMEOUT`:e.includes(`memory`)||e.includes(`Memory`)?`MEMORY`:e.includes(`Worker`)&&e.includes(`exited`)?`WORKER_CRASH`:e.includes(`initialization`)||e.includes(`init`)?`INITIALIZATION`:`UNKNOWN`}isRecoverableError(e){return[`timeout`,`Worker being recycled`,`Worker pool cerrado`,`temporary`].some(t=>e.toLowerCase().includes(t.toLowerCase()))}}
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript Worker Pool - Pool de workers para compilación paralela
|
|
3
|
+
* Reemplaza el worker único con múltiples workers para aprovecha la concurrencia real
|
|
4
|
+
*/
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as process from 'node:process';
|
|
8
|
+
import { setImmediate } from 'node:timers';
|
|
9
|
+
import { Worker } from 'node:worker_threads';
|
|
10
|
+
import { validateTypesWithLanguageService } from './typescript-sync-validator.js';
|
|
11
|
+
/**
|
|
12
|
+
* Pool de workers para compilación TypeScript paralela
|
|
13
|
+
* Distribuye las tareas entre múltiples workers para mayor rendimiento
|
|
14
|
+
*/
|
|
15
|
+
export class TypeScriptWorkerPool {
|
|
16
|
+
static instance;
|
|
17
|
+
workers = [];
|
|
18
|
+
poolSize;
|
|
19
|
+
workerPath;
|
|
20
|
+
initPromise = null;
|
|
21
|
+
isInitialized = false; // Configuración optimizada con reciclaje de workers
|
|
22
|
+
TASK_TIMEOUT = 8000; // 8 segundos por tarea (reducido para mayor velocidad)
|
|
23
|
+
WORKER_INIT_TIMEOUT = 3000; // 3 segundos para inicializar (reducido)
|
|
24
|
+
MAX_TASKS_PER_WORKER = 200; // ✨ OPTIMIZADO: Aumentado de 50 a 200 para reducir overhead de reciclaje
|
|
25
|
+
WORKER_MEMORY_CHECK_INTERVAL = 500; // ✨ OPTIMIZADO: Verificar cada 500 tareas (reducir overhead)
|
|
26
|
+
// ✨ FIX #1: Referencias a timers para limpieza adecuada
|
|
27
|
+
memoryCheckInterval = null;
|
|
28
|
+
cleanupInterval = null;
|
|
29
|
+
// Métricas de rendimiento
|
|
30
|
+
totalTasks = 0;
|
|
31
|
+
completedTasks = 0;
|
|
32
|
+
failedTasks = 0;
|
|
33
|
+
constructor() {
|
|
34
|
+
// Determinar tamaño óptimo del pool - MÁS AGRESIVO para mejor rendimiento
|
|
35
|
+
const cpuCount = os.cpus().length;
|
|
36
|
+
// Usar más workers para aprovechar mejor el CPU
|
|
37
|
+
this.poolSize = Math.min(Math.max(cpuCount, 4), 16); // Entre 4 y 16 workers
|
|
38
|
+
this.workerPath = path.join(process.env.PATH_PROY || path.join(process.cwd(), 'src'), 'compiler', 'typescript-worker-thread.cjs');
|
|
39
|
+
// ✨ ISSUE #4: Configurar monitoreo de memoria automático this.startMemoryMonitoring();
|
|
40
|
+
}
|
|
41
|
+
// ✨ ISSUE #4: Métodos de control de memoria y timeouts
|
|
42
|
+
/**
|
|
43
|
+
* Inicia el monitoreo automático de memoria de workers
|
|
44
|
+
* ✨ FIX #1: Ahora almacena referencias a los intervalos para limpieza posterior
|
|
45
|
+
*/
|
|
46
|
+
startMemoryMonitoring() {
|
|
47
|
+
// Limpiar intervalos previos si existen
|
|
48
|
+
this.stopMemoryMonitoring();
|
|
49
|
+
// Monitoreo cada 15 segundos (más frecuente)
|
|
50
|
+
this.memoryCheckInterval = setInterval(() => {
|
|
51
|
+
this.checkWorkersMemory();
|
|
52
|
+
}, 15000);
|
|
53
|
+
// Limpieza de workers inactivos cada 2 minutos (más frecuente)
|
|
54
|
+
this.cleanupInterval = setInterval(() => {
|
|
55
|
+
this.cleanupInactiveWorkers();
|
|
56
|
+
}, 120000);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* ✨ FIX #1: Detiene el monitoreo automático de memoria
|
|
60
|
+
*/
|
|
61
|
+
stopMemoryMonitoring() {
|
|
62
|
+
if (this.memoryCheckInterval) {
|
|
63
|
+
clearInterval(this.memoryCheckInterval);
|
|
64
|
+
this.memoryCheckInterval = null;
|
|
65
|
+
}
|
|
66
|
+
if (this.cleanupInterval) {
|
|
67
|
+
clearInterval(this.cleanupInterval);
|
|
68
|
+
this.cleanupInterval = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Verifica el uso de memoria de todos los workers con medición real
|
|
73
|
+
*/
|
|
74
|
+
async checkWorkersMemory() {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
for (const poolWorker of this.workers) {
|
|
77
|
+
// Actualizar tiempo de última verificación
|
|
78
|
+
poolWorker.lastMemoryCheck = now;
|
|
79
|
+
try {
|
|
80
|
+
// ✨ ISSUE #4: Obtener memoria real del worker
|
|
81
|
+
const memoryInfo = await this.getWorkerMemoryUsage(poolWorker);
|
|
82
|
+
poolWorker.memoryUsage = memoryInfo.heapUsed;
|
|
83
|
+
// Verificar límites de memoria y reciclaje automático
|
|
84
|
+
if (this.shouldRecycleWorker(poolWorker)) {
|
|
85
|
+
const reason = this.getRecycleReason(poolWorker);
|
|
86
|
+
console.warn(`[WorkerPool] Worker ${poolWorker.id} requiere reciclaje: ${reason}`);
|
|
87
|
+
await this.recycleWorker(poolWorker);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.warn(`[WorkerPool] Error verificando memoria del worker ${poolWorker.id}:`, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Obtiene el uso real de memoria de un worker
|
|
97
|
+
*/
|
|
98
|
+
async getWorkerMemoryUsage(poolWorker) {
|
|
99
|
+
return new Promise(resolve => {
|
|
100
|
+
const timeout = setTimeout(() => {
|
|
101
|
+
// Fallback con estimación si no hay respuesta
|
|
102
|
+
resolve({
|
|
103
|
+
heapUsed: poolWorker.tasksProcessed * 2048, // 2KB por tarea
|
|
104
|
+
heapTotal: poolWorker.tasksProcessed * 3072, // 3KB total estimado
|
|
105
|
+
rss: poolWorker.tasksProcessed * 4096, // 4KB RSS estimado
|
|
106
|
+
});
|
|
107
|
+
}, 1000);
|
|
108
|
+
// Solicitar memoria real del worker
|
|
109
|
+
const memoryRequestId = `memory-${poolWorker.id}-${Date.now()}`;
|
|
110
|
+
const handler = (response) => {
|
|
111
|
+
if (response.id === memoryRequestId &&
|
|
112
|
+
response.type === 'memory-usage') {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
poolWorker.worker.off('message', handler);
|
|
115
|
+
resolve({
|
|
116
|
+
heapUsed: response.memoryUsage?.heapUsed ||
|
|
117
|
+
poolWorker.tasksProcessed * 2048,
|
|
118
|
+
heapTotal: response.memoryUsage?.heapTotal ||
|
|
119
|
+
poolWorker.tasksProcessed * 3072,
|
|
120
|
+
rss: response.memoryUsage?.rss ||
|
|
121
|
+
poolWorker.tasksProcessed * 4096,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
poolWorker.worker.on('message', handler);
|
|
126
|
+
poolWorker.worker.postMessage({
|
|
127
|
+
type: 'get-memory-usage',
|
|
128
|
+
id: memoryRequestId,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Obtiene la razón por la cual un worker debe ser reciclado
|
|
134
|
+
* ✨ OPTIMIZACIÓN #11: Límites aumentados para reducir overhead de reciclaje
|
|
135
|
+
*/
|
|
136
|
+
getRecycleReason(poolWorker) {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const MEMORY_LIMIT = 100 * 1024 * 1024; // ✨ 100MB (aumentado de 50MB)
|
|
139
|
+
const TIME_LIMIT = 30 * 60 * 1000; // 30 minutos
|
|
140
|
+
const TASK_LIMIT = this.MAX_TASKS_PER_WORKER;
|
|
141
|
+
const reasons = [];
|
|
142
|
+
if (poolWorker.memoryUsage > MEMORY_LIMIT) {
|
|
143
|
+
reasons.push(`memoria excede ${Math.round(MEMORY_LIMIT / 1024 / 1024)}MB (actual: ${Math.round(poolWorker.memoryUsage / 1024 / 1024)}MB)`);
|
|
144
|
+
}
|
|
145
|
+
if (now - poolWorker.creationTime > TIME_LIMIT) {
|
|
146
|
+
reasons.push(`tiempo de vida excede ${Math.round(TIME_LIMIT / 60000)} minutos`);
|
|
147
|
+
}
|
|
148
|
+
if (poolWorker.tasksProcessed >= TASK_LIMIT) {
|
|
149
|
+
reasons.push(`tareas procesadas exceden ${TASK_LIMIT} (actual: ${poolWorker.tasksProcessed})`);
|
|
150
|
+
}
|
|
151
|
+
return reasons.join(', ');
|
|
152
|
+
} /**
|
|
153
|
+
* Limpia workers que han estado inactivos por mucho tiempo
|
|
154
|
+
*/
|
|
155
|
+
async cleanupInactiveWorkers() {
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const INACTIVE_TIMEOUT = 5 * 60 * 1000; // 5 minutos (reducido)
|
|
158
|
+
for (const poolWorker of this.workers) {
|
|
159
|
+
const timeSinceLastActivity = now - poolWorker.lastActivityTime;
|
|
160
|
+
if (timeSinceLastActivity > INACTIVE_TIMEOUT &&
|
|
161
|
+
!poolWorker.busy &&
|
|
162
|
+
poolWorker.pendingTasks.size === 0) {
|
|
163
|
+
console.info(`[WorkerPool] Worker ${poolWorker.id} inactivo por ${Math.round(timeSinceLastActivity / 60000)} minutos, reciclando...`);
|
|
164
|
+
await this.recycleWorker(poolWorker);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} /**
|
|
168
|
+
* Verifica si un worker debe ser reciclado por límites de memoria/tiempo
|
|
169
|
+
* ✨ OPTIMIZACIÓN #11: Límites aumentados para reducir overhead de reciclaje
|
|
170
|
+
*/
|
|
171
|
+
shouldRecycleWorker(poolWorker) {
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
const MEMORY_LIMIT = 100 * 1024 * 1024; // ✨ 100MB (aumentado de 30MB)
|
|
174
|
+
const TIME_LIMIT = 30 * 60 * 1000; // ✨ 30 minutos (aumentado de 15 min)
|
|
175
|
+
const TASK_LIMIT = this.MAX_TASKS_PER_WORKER;
|
|
176
|
+
return (poolWorker.memoryUsage > MEMORY_LIMIT ||
|
|
177
|
+
now - poolWorker.creationTime > TIME_LIMIT ||
|
|
178
|
+
poolWorker.tasksProcessed >= TASK_LIMIT);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Obtiene la instancia singleton del Worker Pool
|
|
182
|
+
*/
|
|
183
|
+
static getInstance() {
|
|
184
|
+
if (!TypeScriptWorkerPool.instance) {
|
|
185
|
+
TypeScriptWorkerPool.instance = new TypeScriptWorkerPool();
|
|
186
|
+
}
|
|
187
|
+
return TypeScriptWorkerPool.instance;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Configura el modo de operación del pool - OPTIMIZADO para máxima velocidad
|
|
191
|
+
*/
|
|
192
|
+
setMode(mode) {
|
|
193
|
+
// Ajustar configuración según el modo - MÁS AGRESIVO
|
|
194
|
+
switch (mode) {
|
|
195
|
+
case 'batch':
|
|
196
|
+
// Para modo batch, máxima concurrencia para throughput
|
|
197
|
+
this.poolSize = Math.min(os.cpus().length, 20);
|
|
198
|
+
break;
|
|
199
|
+
case 'watch':
|
|
200
|
+
// Para modo watch, más workers para mejor responsividad
|
|
201
|
+
this.poolSize = Math.min(os.cpus().length, 12);
|
|
202
|
+
break;
|
|
203
|
+
case 'individual':
|
|
204
|
+
// Para individual, pool moderado
|
|
205
|
+
this.poolSize = Math.min(8, os.cpus().length);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Inicializa el pool de workers
|
|
211
|
+
*/
|
|
212
|
+
async initializePool() {
|
|
213
|
+
if (this.initPromise) {
|
|
214
|
+
return this.initPromise;
|
|
215
|
+
}
|
|
216
|
+
this.initPromise = this._performPoolInitialization();
|
|
217
|
+
return this.initPromise;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Realiza la inicialización del pool de workers
|
|
221
|
+
*/
|
|
222
|
+
async _performPoolInitialization() {
|
|
223
|
+
try {
|
|
224
|
+
// Verificar que el archivo del worker existe
|
|
225
|
+
const fs = await import('node:fs');
|
|
226
|
+
const exists = fs.existsSync(this.workerPath);
|
|
227
|
+
if (!exists) {
|
|
228
|
+
throw new Error(`Worker thread file not found: ${this.workerPath}`);
|
|
229
|
+
}
|
|
230
|
+
// Crear workers en paralelo
|
|
231
|
+
const workerPromises = Array.from({ length: this.poolSize }, (_, index) => this.createWorker(index));
|
|
232
|
+
this.workers = await Promise.all(workerPromises);
|
|
233
|
+
this.isInitialized = true;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
this.isInitialized = false;
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Crea un worker individual
|
|
242
|
+
*/
|
|
243
|
+
async createWorker(workerId) {
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
try {
|
|
246
|
+
const worker = new Worker(this.workerPath, {
|
|
247
|
+
env: {
|
|
248
|
+
...process.env,
|
|
249
|
+
NODE_OPTIONS: '',
|
|
250
|
+
WORKER_ID: workerId.toString(),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
const poolWorker = {
|
|
254
|
+
worker,
|
|
255
|
+
id: workerId,
|
|
256
|
+
busy: false,
|
|
257
|
+
pendingTasks: new Map(),
|
|
258
|
+
taskCounter: 0,
|
|
259
|
+
// ✨ ISSUE #4: Inicializar controles de memoria
|
|
260
|
+
memoryUsage: 0,
|
|
261
|
+
lastMemoryCheck: Date.now(),
|
|
262
|
+
tasksProcessed: 0,
|
|
263
|
+
creationTime: Date.now(),
|
|
264
|
+
lastActivityTime: Date.now(),
|
|
265
|
+
};
|
|
266
|
+
// Configurar listeners
|
|
267
|
+
this.setupWorkerListeners(poolWorker);
|
|
268
|
+
// Timeout para inicialización
|
|
269
|
+
const initTimeout = setTimeout(() => {
|
|
270
|
+
reject(new Error(`Worker ${workerId} initialization timeout`));
|
|
271
|
+
}, this.WORKER_INIT_TIMEOUT);
|
|
272
|
+
// Esperar que el worker esté listo
|
|
273
|
+
const checkReady = () => {
|
|
274
|
+
worker.postMessage({ type: 'ping' });
|
|
275
|
+
};
|
|
276
|
+
worker.on('message', function readyHandler(response) {
|
|
277
|
+
if (response.id === 'worker-ready' ||
|
|
278
|
+
response.message === 'pong') {
|
|
279
|
+
clearTimeout(initTimeout);
|
|
280
|
+
worker.off('message', readyHandler);
|
|
281
|
+
resolve(poolWorker);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
worker.on('error', (error) => {
|
|
285
|
+
clearTimeout(initTimeout);
|
|
286
|
+
reject(error);
|
|
287
|
+
});
|
|
288
|
+
// Intentar conectar
|
|
289
|
+
checkReady();
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
reject(error);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Configura los listeners para un worker individual
|
|
298
|
+
*/
|
|
299
|
+
setupWorkerListeners(poolWorker) {
|
|
300
|
+
const { worker } = poolWorker;
|
|
301
|
+
worker.on('message', (response) => {
|
|
302
|
+
try {
|
|
303
|
+
// Ignorar mensajes de control
|
|
304
|
+
if (response.id === 'worker-ready' ||
|
|
305
|
+
response.message === 'pong') {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// ✨ ISSUE #4: Manejar reportes de memoria del worker
|
|
309
|
+
if (response.type === 'memory-usage') {
|
|
310
|
+
return; // Ya manejado en getWorkerMemoryUsage
|
|
311
|
+
}
|
|
312
|
+
// Buscar la tarea pendiente
|
|
313
|
+
const pendingTask = poolWorker.pendingTasks.get(response.id);
|
|
314
|
+
if (!pendingTask) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// ✨ FIX MEMORIA: Limpiar timeout INMEDIATAMENTE para liberar referencias
|
|
318
|
+
clearTimeout(pendingTask.timeout);
|
|
319
|
+
// ✨ FIX MEMORIA: Guardar referencias necesarias antes de eliminar la tarea
|
|
320
|
+
const { resolve, reject } = pendingTask;
|
|
321
|
+
// ✨ FIX MEMORIA: Eliminar la tarea del mapa ANTES de procesarla
|
|
322
|
+
poolWorker.pendingTasks.delete(response.id);
|
|
323
|
+
// Marcar worker como disponible si no tiene más tareas
|
|
324
|
+
if (poolWorker.pendingTasks.size === 0) {
|
|
325
|
+
poolWorker.busy = false;
|
|
326
|
+
}
|
|
327
|
+
// Actualizar actividad del worker
|
|
328
|
+
poolWorker.lastActivityTime = Date.now();
|
|
329
|
+
// ✨ ISSUE #4: Manejo mejorado de errores de TypeScript
|
|
330
|
+
if (response.success &&
|
|
331
|
+
response.diagnostics !== undefined &&
|
|
332
|
+
response.hasErrors !== undefined) {
|
|
333
|
+
this.completedTasks++;
|
|
334
|
+
// ✨ FIX MEMORIA: Crear resultado sin referencias circulares
|
|
335
|
+
const result = {
|
|
336
|
+
diagnostics: response.diagnostics || [],
|
|
337
|
+
hasErrors: response.hasErrors,
|
|
338
|
+
};
|
|
339
|
+
// ✨ FIX MEMORIA: No mantener response completo en memoria
|
|
340
|
+
resolve(result);
|
|
341
|
+
// ✨ FIX MEMORIA: Limpiar diagnostics después de resolver
|
|
342
|
+
setImmediate(() => {
|
|
343
|
+
if (response.diagnostics) {
|
|
344
|
+
response.diagnostics.length = 0;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
this.failedTasks++;
|
|
350
|
+
const errorMessage = response.error || 'Error desconocido del worker';
|
|
351
|
+
// ✨ FIX MEMORIA: Error simple sin referencias pesadas
|
|
352
|
+
const error = new Error(errorMessage);
|
|
353
|
+
reject(error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// Error silencioso - no imprimir cada error
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
worker.on('error', async (error) => {
|
|
361
|
+
await this.handleWorkerError(poolWorker, error);
|
|
362
|
+
});
|
|
363
|
+
worker.on('exit', async (code) => {
|
|
364
|
+
await this.handleWorkerExit(poolWorker, code);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Maneja errores de un worker específico con cleanup completo
|
|
369
|
+
* ✨ FIX MEMORIA: Limpieza agresiva y sin esperas
|
|
370
|
+
*/
|
|
371
|
+
async handleWorkerError(poolWorker, error) {
|
|
372
|
+
console.warn(`[WorkerPool] Manejando error del worker ${poolWorker.id}:`, error.message);
|
|
373
|
+
// 1. ✨ FIX MEMORIA: Limpiar INMEDIATAMENTE todas las tareas pendientes
|
|
374
|
+
const pendingTasksArray = Array.from(poolWorker.pendingTasks.entries());
|
|
375
|
+
for (const [taskId, task] of pendingTasksArray) {
|
|
376
|
+
clearTimeout(task.timeout);
|
|
377
|
+
this.failedTasks++;
|
|
378
|
+
task.reject(new Error(`Worker ${poolWorker.id} failed`));
|
|
379
|
+
poolWorker.pendingTasks.delete(taskId);
|
|
380
|
+
}
|
|
381
|
+
poolWorker.pendingTasks.clear();
|
|
382
|
+
// 2. ✨ FIX MEMORIA: Remover listeners antes de terminar
|
|
383
|
+
try {
|
|
384
|
+
poolWorker.worker.removeAllListeners('message');
|
|
385
|
+
poolWorker.worker.removeAllListeners('error');
|
|
386
|
+
poolWorker.worker.removeAllListeners('exit');
|
|
387
|
+
poolWorker.worker.removeAllListeners('online');
|
|
388
|
+
poolWorker.worker.removeAllListeners('messageerror');
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Silencioso
|
|
392
|
+
}
|
|
393
|
+
// 3. ✨ FIX MEMORIA: Terminar sin esperar
|
|
394
|
+
const workerToTerminate = poolWorker.worker;
|
|
395
|
+
poolWorker.worker = null;
|
|
396
|
+
poolWorker.busy = false;
|
|
397
|
+
workerToTerminate.terminate().catch(() => {
|
|
398
|
+
// Silencioso
|
|
399
|
+
});
|
|
400
|
+
// 4. ✨ FIX MEMORIA: Recrear worker asíncronamente sin bloquear
|
|
401
|
+
if (this.isInitialized && this.workers.length > 0) {
|
|
402
|
+
setImmediate(async () => {
|
|
403
|
+
try {
|
|
404
|
+
const newWorker = await this.createWorker(poolWorker.id);
|
|
405
|
+
const index = this.workers.findIndex(w => w.id === poolWorker.id);
|
|
406
|
+
if (index !== -1) {
|
|
407
|
+
this.workers[index] = newWorker;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Silencioso
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Maneja la salida inesperada de un worker con cleanup completo
|
|
418
|
+
*/
|
|
419
|
+
async handleWorkerExit(poolWorker, code) {
|
|
420
|
+
console.warn(`[WorkerPool] Worker ${poolWorker.id} salió con código ${code}`);
|
|
421
|
+
// 1. Rechazar tareas pendientes con cleanup
|
|
422
|
+
poolWorker.pendingTasks.forEach(task => {
|
|
423
|
+
clearTimeout(task.timeout);
|
|
424
|
+
this.failedTasks++;
|
|
425
|
+
task.reject(new Error(`Worker ${poolWorker.id} exited with code ${code}`));
|
|
426
|
+
});
|
|
427
|
+
poolWorker.pendingTasks.clear();
|
|
428
|
+
poolWorker.busy = false;
|
|
429
|
+
// 2. Limpiar listeners para evitar memory leaks
|
|
430
|
+
try {
|
|
431
|
+
poolWorker.worker.removeAllListeners();
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Error silencioso en cleanup
|
|
435
|
+
}
|
|
436
|
+
// 3. Recrear worker si es necesario y el pool está activo
|
|
437
|
+
if (this.isInitialized && this.workers.length > 0) {
|
|
438
|
+
try {
|
|
439
|
+
await this.recreateWorker(poolWorker);
|
|
440
|
+
}
|
|
441
|
+
catch (recreateError) {
|
|
442
|
+
console.error(`[WorkerPool] Error recreando worker ${poolWorker.id}:`, recreateError);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Recrea un worker que falló
|
|
448
|
+
*/
|
|
449
|
+
async recreateWorker(failedWorker) {
|
|
450
|
+
try {
|
|
451
|
+
const newWorker = await this.createWorker(failedWorker.id);
|
|
452
|
+
// Reemplazar en el array
|
|
453
|
+
const index = this.workers.findIndex(w => w.id === failedWorker.id);
|
|
454
|
+
if (index !== -1) {
|
|
455
|
+
this.workers[index] = newWorker;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
// Error silencioso en recreación
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Recicla un worker para prevenir memory leaks
|
|
464
|
+
* ✨ FIX MEMORIA: Limpieza agresiva de todas las referencias
|
|
465
|
+
*/
|
|
466
|
+
async recycleWorker(poolWorker) {
|
|
467
|
+
try {
|
|
468
|
+
console.log(`[WorkerPool] Reciclando worker ${poolWorker.id} después de ${poolWorker.taskCounter} tareas`);
|
|
469
|
+
// 1. ✨ FIX MEMORIA: No esperar, rechazar inmediatamente todas las tareas pendientes
|
|
470
|
+
const pendingTasksArray = Array.from(poolWorker.pendingTasks.entries());
|
|
471
|
+
for (const [taskId, task] of pendingTasksArray) {
|
|
472
|
+
clearTimeout(task.timeout);
|
|
473
|
+
task.reject(new Error('Worker being recycled'));
|
|
474
|
+
poolWorker.pendingTasks.delete(taskId);
|
|
475
|
+
}
|
|
476
|
+
// ✨ FIX MEMORIA: Forzar limpieza del Map
|
|
477
|
+
poolWorker.pendingTasks.clear();
|
|
478
|
+
// 2. ✨ FIX MEMORIA: Remover listeners explícitamente por tipo
|
|
479
|
+
const worker = poolWorker.worker;
|
|
480
|
+
worker.removeAllListeners('message');
|
|
481
|
+
worker.removeAllListeners('error');
|
|
482
|
+
worker.removeAllListeners('exit');
|
|
483
|
+
worker.removeAllListeners('online');
|
|
484
|
+
worker.removeAllListeners('messageerror');
|
|
485
|
+
// 3. ✨ FIX MEMORIA: Guardar referencia temporal y limpiar poolWorker
|
|
486
|
+
const workerToTerminate = poolWorker.worker;
|
|
487
|
+
const workerId = poolWorker.id;
|
|
488
|
+
// Limpiar todas las propiedades del poolWorker
|
|
489
|
+
poolWorker.worker = null;
|
|
490
|
+
poolWorker.busy = false;
|
|
491
|
+
poolWorker.taskCounter = 0;
|
|
492
|
+
poolWorker.tasksProcessed = 0;
|
|
493
|
+
poolWorker.memoryUsage = 0;
|
|
494
|
+
// 4. ✨ FIX MEMORIA: Terminar con timeout agresivo
|
|
495
|
+
const terminatePromise = workerToTerminate.terminate();
|
|
496
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Terminate timeout')), 2000));
|
|
497
|
+
await Promise.race([terminatePromise, timeoutPromise]).catch(() => {
|
|
498
|
+
// Forzar terminación si falla
|
|
499
|
+
try {
|
|
500
|
+
workerToTerminate.terminate();
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
// Silencioso
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
// ✨ FIX MEMORIA: Forzar garbage collection del worker terminado
|
|
507
|
+
setImmediate(() => {
|
|
508
|
+
// Dar tiempo al GC para limpiar
|
|
509
|
+
});
|
|
510
|
+
// 5. ✨ FIX MEMORIA: Crear y reemplazar con nuevo worker
|
|
511
|
+
const newWorker = await this.createWorker(workerId);
|
|
512
|
+
const index = this.workers.findIndex(w => w.id === workerId);
|
|
513
|
+
if (index !== -1) {
|
|
514
|
+
this.workers[index] = newWorker;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
console.error(`[WorkerPool] Error reciclando worker ${poolWorker.id}:`, error);
|
|
519
|
+
// ✨ FIX MEMORIA: Asegurar limpieza incluso en error
|
|
520
|
+
poolWorker.pendingTasks.clear();
|
|
521
|
+
poolWorker.busy = false;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Encuentra el worker menos ocupado
|
|
526
|
+
*/
|
|
527
|
+
findAvailableWorker() {
|
|
528
|
+
// Buscar worker completamente libre
|
|
529
|
+
const freeWorker = this.workers.find(w => !w.busy && w.pendingTasks.size === 0);
|
|
530
|
+
if (freeWorker) {
|
|
531
|
+
return freeWorker;
|
|
532
|
+
}
|
|
533
|
+
// Si no hay workers libres, buscar el menos ocupado
|
|
534
|
+
const leastBusyWorker = this.workers.reduce((least, current) => {
|
|
535
|
+
if (current.pendingTasks.size < least.pendingTasks.size) {
|
|
536
|
+
return current;
|
|
537
|
+
}
|
|
538
|
+
return least;
|
|
539
|
+
});
|
|
540
|
+
// Solo devolver si no está demasiado ocupado
|
|
541
|
+
if (leastBusyWorker.pendingTasks.size < 5) {
|
|
542
|
+
return leastBusyWorker;
|
|
543
|
+
}
|
|
544
|
+
return null; // Todos los workers están muy ocupados
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Realiza type checking usando el pool de workers
|
|
548
|
+
*/
|
|
549
|
+
async typeCheck(fileName, content, compilerOptions) {
|
|
550
|
+
// Asegurar que el pool esté inicializado
|
|
551
|
+
await this.initializePool();
|
|
552
|
+
if (!this.isInitialized) {
|
|
553
|
+
return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
|
|
554
|
+
}
|
|
555
|
+
// Buscar worker disponible
|
|
556
|
+
const availableWorker = this.findAvailableWorker();
|
|
557
|
+
// ✨ FIX: Incrementar totalTasks ANTES del try/catch para conteo correcto
|
|
558
|
+
this.totalTasks++;
|
|
559
|
+
if (!availableWorker) {
|
|
560
|
+
return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
return await this.typeCheckWithWorker(availableWorker, fileName, content, compilerOptions);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Realiza type checking usando un worker específico con reciclaje automático
|
|
571
|
+
* ✨ FIX MEMORIA: Optimizado para prevenir fugas de memoria
|
|
572
|
+
*/
|
|
573
|
+
async typeCheckWithWorker(poolWorker, fileName, content, compilerOptions) {
|
|
574
|
+
// Verificar si el worker necesita reciclaje por número de tareas
|
|
575
|
+
if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
|
|
576
|
+
// ✨ FIX: No esperar el reciclaje, usar fallback
|
|
577
|
+
return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
|
|
578
|
+
}
|
|
579
|
+
return new Promise((resolve, reject) => {
|
|
580
|
+
const taskId = `task-${poolWorker.id}-${++poolWorker.taskCounter}-${Date.now()}`;
|
|
581
|
+
// Marcar worker como ocupado y actualizar actividad
|
|
582
|
+
poolWorker.busy = true;
|
|
583
|
+
poolWorker.lastActivityTime = Date.now();
|
|
584
|
+
poolWorker.tasksProcessed++;
|
|
585
|
+
// ✨ ISSUE #4: Timeout dinámico basado en complejidad del archivo
|
|
586
|
+
const dynamicTimeout = this.calculateDynamicTimeout(fileName, content, compilerOptions);
|
|
587
|
+
// ✨ FIX MEMORIA: Configurar timeout con limpieza explícita
|
|
588
|
+
const timeout = setTimeout(() => {
|
|
589
|
+
const task = poolWorker.pendingTasks.get(taskId);
|
|
590
|
+
if (task) {
|
|
591
|
+
poolWorker.pendingTasks.delete(taskId);
|
|
592
|
+
if (poolWorker.pendingTasks.size === 0) {
|
|
593
|
+
poolWorker.busy = false;
|
|
594
|
+
}
|
|
595
|
+
reject(new Error(`Timeout (${dynamicTimeout}ms) en type checking para ${fileName}`));
|
|
596
|
+
}
|
|
597
|
+
}, dynamicTimeout);
|
|
598
|
+
// ✨ FIX MEMORIA: Wrapper para resolver/rechazar que limpia el timeout
|
|
599
|
+
const wrappedResolve = (result) => {
|
|
600
|
+
clearTimeout(timeout);
|
|
601
|
+
resolve(result);
|
|
602
|
+
};
|
|
603
|
+
const wrappedReject = (error) => {
|
|
604
|
+
clearTimeout(timeout);
|
|
605
|
+
reject(error);
|
|
606
|
+
};
|
|
607
|
+
// ✨ FIX MEMORIA: Agregar tarea con wrappers que auto-limpian
|
|
608
|
+
poolWorker.pendingTasks.set(taskId, {
|
|
609
|
+
resolve: wrappedResolve,
|
|
610
|
+
reject: wrappedReject,
|
|
611
|
+
timeout,
|
|
612
|
+
fileName,
|
|
613
|
+
startTime: Date.now(),
|
|
614
|
+
});
|
|
615
|
+
try {
|
|
616
|
+
// ✨ FIX: Pasar todas las opciones relevantes del tsconfig.json de forma serializable
|
|
617
|
+
// Crear copia limpia con solo valores primitivos (sin funciones, símbolos, etc.)
|
|
618
|
+
const serializableOptions = {};
|
|
619
|
+
for (const key in compilerOptions) {
|
|
620
|
+
const value = compilerOptions[key];
|
|
621
|
+
// Solo pasar valores serializables (primitivos, arrays, objetos simples)
|
|
622
|
+
if (value !== undefined &&
|
|
623
|
+
value !== null &&
|
|
624
|
+
typeof value !== 'function' &&
|
|
625
|
+
typeof value !== 'symbol') {
|
|
626
|
+
serializableOptions[key] = value;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const message = {
|
|
630
|
+
id: taskId,
|
|
631
|
+
fileName,
|
|
632
|
+
content,
|
|
633
|
+
compilerOptions: serializableOptions,
|
|
634
|
+
};
|
|
635
|
+
// Enviar mensaje al worker
|
|
636
|
+
poolWorker.worker.postMessage(message);
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
// Limpiar en caso de error
|
|
640
|
+
clearTimeout(timeout);
|
|
641
|
+
poolWorker.pendingTasks.delete(taskId);
|
|
642
|
+
if (poolWorker.pendingTasks.size === 0) {
|
|
643
|
+
poolWorker.busy = false;
|
|
644
|
+
}
|
|
645
|
+
reject(error);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Calcula un timeout dinámico basado en la complejidad del archivo TypeScript
|
|
651
|
+
*/
|
|
652
|
+
calculateDynamicTimeout(fileName, content, compilerOptions) {
|
|
653
|
+
const baseTimeout = this.TASK_TIMEOUT; // 10 segundos base
|
|
654
|
+
let complexityMultiplier = 1;
|
|
655
|
+
// Factor 1: Tamaño del archivo
|
|
656
|
+
const contentLength = content.length;
|
|
657
|
+
if (contentLength > 100000) {
|
|
658
|
+
// Archivos > 100KB
|
|
659
|
+
complexityMultiplier += 1.5;
|
|
660
|
+
}
|
|
661
|
+
else if (contentLength > 50000) {
|
|
662
|
+
// Archivos > 50KB
|
|
663
|
+
complexityMultiplier += 1;
|
|
664
|
+
}
|
|
665
|
+
else if (contentLength > 20000) {
|
|
666
|
+
// Archivos > 20KB
|
|
667
|
+
complexityMultiplier += 0.5;
|
|
668
|
+
}
|
|
669
|
+
// Factor 2: Complejidad sintáctica
|
|
670
|
+
const importCount = (content.match(/^import\s+/gm) || []).length;
|
|
671
|
+
const typeCount = (content.match(/\btype\s+\w+/g) || []).length;
|
|
672
|
+
const interfaceCount = (content.match(/\binterface\s+\w+/g) || [])
|
|
673
|
+
.length;
|
|
674
|
+
const genericCount = (content.match(/<[^>]*>/g) || []).length;
|
|
675
|
+
const complexConstructs = importCount + typeCount + interfaceCount + genericCount * 0.5;
|
|
676
|
+
if (complexConstructs > 100) {
|
|
677
|
+
complexityMultiplier += 2;
|
|
678
|
+
}
|
|
679
|
+
else if (complexConstructs > 50) {
|
|
680
|
+
complexityMultiplier += 1;
|
|
681
|
+
}
|
|
682
|
+
else if (complexConstructs > 20) {
|
|
683
|
+
complexityMultiplier += 0.5;
|
|
684
|
+
}
|
|
685
|
+
// Factor 3: Configuración de TypeScript estricta
|
|
686
|
+
if (compilerOptions?.strict || compilerOptions?.noImplicitAny) {
|
|
687
|
+
complexityMultiplier += 0.3;
|
|
688
|
+
}
|
|
689
|
+
// Factor 4: Extensión de archivo compleja (.d.ts, .vue.ts, etc.)
|
|
690
|
+
if (fileName.includes('.d.ts')) {
|
|
691
|
+
complexityMultiplier += 1; // Los archivos de definición son más complejos
|
|
692
|
+
}
|
|
693
|
+
else if (fileName.includes('.vue')) {
|
|
694
|
+
complexityMultiplier += 0.5; // Los archivos Vue requieren procesamiento adicional
|
|
695
|
+
}
|
|
696
|
+
// Aplicar límites razonables
|
|
697
|
+
complexityMultiplier = Math.min(complexityMultiplier, 5); // Máximo 5x el timeout base
|
|
698
|
+
complexityMultiplier = Math.max(complexityMultiplier, 0.5); // Mínimo 0.5x el timeout base
|
|
699
|
+
const finalTimeout = Math.round(baseTimeout * complexityMultiplier);
|
|
700
|
+
return Math.min(finalTimeout, 60000); // Máximo absoluto de 60 segundos
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Fallback síncrono para type checking
|
|
704
|
+
* ✨ FIX: Ahora trackea las tareas correctamente
|
|
705
|
+
*/
|
|
706
|
+
typeCheckWithSyncFallback(fileName, content, compilerOptions) {
|
|
707
|
+
try {
|
|
708
|
+
const result = validateTypesWithLanguageService(fileName, content, compilerOptions);
|
|
709
|
+
this.completedTasks++; // ✨ FIX: Contabilizar tareas completadas en fallback
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
this.failedTasks++; // ✨ FIX: Contabilizar tareas fallidas en fallback
|
|
714
|
+
return {
|
|
715
|
+
diagnostics: [],
|
|
716
|
+
hasErrors: false,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Cierra todos los workers del pool con cleanup completo
|
|
722
|
+
* ✨ FIX #1: Ahora limpia timers de monitoreo para evitar fugas de memoria
|
|
723
|
+
*/
|
|
724
|
+
async terminate() {
|
|
725
|
+
console.log('[WorkerPool] Cerrando pool de workers...');
|
|
726
|
+
// 0. ✨ FIX #1: Detener monitoreo de memoria primero
|
|
727
|
+
this.stopMemoryMonitoring();
|
|
728
|
+
// 1. Rechazar todas las tareas pendientes con cleanup
|
|
729
|
+
let totalPendingTasks = 0;
|
|
730
|
+
for (const poolWorker of this.workers) {
|
|
731
|
+
totalPendingTasks += poolWorker.pendingTasks.size;
|
|
732
|
+
poolWorker.pendingTasks.forEach(task => {
|
|
733
|
+
clearTimeout(task.timeout);
|
|
734
|
+
task.reject(new Error('Worker pool cerrado'));
|
|
735
|
+
});
|
|
736
|
+
poolWorker.pendingTasks.clear();
|
|
737
|
+
// Limpiar listeners para evitar memory leaks
|
|
738
|
+
try {
|
|
739
|
+
poolWorker.worker.removeAllListeners();
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
// Error silencioso en cleanup
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (totalPendingTasks > 0) {
|
|
746
|
+
console.log(`[WorkerPool] Se cancelaron ${totalPendingTasks} tareas pendientes`);
|
|
747
|
+
}
|
|
748
|
+
// 2. Cerrar todos los workers con manejo de errores
|
|
749
|
+
const terminatePromises = this.workers.map(async (poolWorker) => {
|
|
750
|
+
try {
|
|
751
|
+
await poolWorker.worker.terminate();
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
console.warn(`[WorkerPool] Error terminando worker ${poolWorker.id}:`, error);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
await Promise.allSettled(terminatePromises);
|
|
758
|
+
// 3. Limpiar estado
|
|
759
|
+
this.workers = [];
|
|
760
|
+
this.isInitialized = false;
|
|
761
|
+
this.initPromise = null;
|
|
762
|
+
console.log(`[WorkerPool] Pool cerrado. Estadísticas finales: ${this.completedTasks} completadas, ${this.failedTasks} fallidas`);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Obtiene estadísticas del pool
|
|
766
|
+
*/
|
|
767
|
+
getStats() {
|
|
768
|
+
const busyWorkers = this.workers.filter(w => w.busy).length;
|
|
769
|
+
const totalPendingTasks = this.workers.reduce((sum, w) => sum + w.pendingTasks.size, 0);
|
|
770
|
+
const successRate = this.totalTasks > 0
|
|
771
|
+
? Math.round((this.completedTasks / this.totalTasks) * 100)
|
|
772
|
+
: 0;
|
|
773
|
+
return {
|
|
774
|
+
poolSize: this.workers.length,
|
|
775
|
+
busyWorkers,
|
|
776
|
+
totalPendingTasks,
|
|
777
|
+
totalTasks: this.totalTasks,
|
|
778
|
+
completedTasks: this.completedTasks,
|
|
779
|
+
failedTasks: this.failedTasks,
|
|
780
|
+
successRate,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
// ✨ ISSUE #4: Métodos de categorización de errores de TypeScript
|
|
784
|
+
/**
|
|
785
|
+
* Categoriza y mejora los errores de TypeScript para mejor debugging
|
|
786
|
+
*/
|
|
787
|
+
categorizeTypeScriptErrors(result, fileName) {
|
|
788
|
+
if (!result.hasErrors || !result.diagnostics?.length) {
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
const categorizedDiagnostics = result.diagnostics.map(diagnostic => {
|
|
792
|
+
// Añadir metadatos útiles para debugging
|
|
793
|
+
const enhanced = {
|
|
794
|
+
...diagnostic,
|
|
795
|
+
_category: this.getErrorCategory(diagnostic),
|
|
796
|
+
_severity: this.getErrorSeverity(diagnostic),
|
|
797
|
+
_fileName: fileName,
|
|
798
|
+
_timestamp: Date.now(),
|
|
799
|
+
};
|
|
800
|
+
return enhanced;
|
|
801
|
+
});
|
|
802
|
+
return {
|
|
803
|
+
...result,
|
|
804
|
+
diagnostics: categorizedDiagnostics,
|
|
805
|
+
// Añadir estadísticas de errores
|
|
806
|
+
_errorStats: this.calculateErrorStats(categorizedDiagnostics),
|
|
807
|
+
};
|
|
808
|
+
} /**
|
|
809
|
+
* Determina la categoría de un error de TypeScript
|
|
810
|
+
*/
|
|
811
|
+
getErrorCategory(diagnostic) {
|
|
812
|
+
const code = diagnostic.code;
|
|
813
|
+
// Categorización basada en códigos de error comunes
|
|
814
|
+
if ([2304, 2339, 2346].includes(code)) {
|
|
815
|
+
return 'MISSING_DECLARATION'; // No puede encontrar nombre/propiedad
|
|
816
|
+
}
|
|
817
|
+
else if ([2322, 2322, 2345].includes(code)) {
|
|
818
|
+
return 'TYPE_MISMATCH'; // Error de tipos
|
|
819
|
+
}
|
|
820
|
+
else if ([2307, 2317].includes(code)) {
|
|
821
|
+
return 'MODULE_RESOLUTION'; // Error de resolución de módulos
|
|
822
|
+
}
|
|
823
|
+
else if ([2552, 2551].includes(code)) {
|
|
824
|
+
return 'PROPERTY_ACCESS'; // Error de acceso a propiedades
|
|
825
|
+
}
|
|
826
|
+
else if (code >= 1000 && code < 2000) {
|
|
827
|
+
return 'SYNTAX_ERROR'; // Errores de sintaxis
|
|
828
|
+
}
|
|
829
|
+
else if (code >= 2000 && code < 3000) {
|
|
830
|
+
return 'SEMANTIC_ERROR'; // Errores semánticos
|
|
831
|
+
}
|
|
832
|
+
else if (code >= 4000) {
|
|
833
|
+
return 'CONFIG_ERROR'; // Errores de configuración
|
|
834
|
+
}
|
|
835
|
+
return 'OTHER';
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Determina la severidad de un error de TypeScript
|
|
839
|
+
*/
|
|
840
|
+
getErrorSeverity(diagnostic) {
|
|
841
|
+
const category = diagnostic.category;
|
|
842
|
+
switch (category) {
|
|
843
|
+
case 1:
|
|
844
|
+
return 'ERROR'; // typescript.DiagnosticCategory.Error
|
|
845
|
+
case 2:
|
|
846
|
+
return 'WARNING'; // typescript.DiagnosticCategory.Warning
|
|
847
|
+
case 3:
|
|
848
|
+
return 'INFO'; // typescript.DiagnosticCategory.Message
|
|
849
|
+
default:
|
|
850
|
+
return 'ERROR';
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Calcula estadísticas de errores para análisis
|
|
855
|
+
*/
|
|
856
|
+
calculateErrorStats(diagnostics) {
|
|
857
|
+
const stats = {
|
|
858
|
+
totalErrors: diagnostics.length,
|
|
859
|
+
errorsByCategory: {},
|
|
860
|
+
errorsBySeverity: {},
|
|
861
|
+
mostCommonError: null,
|
|
862
|
+
};
|
|
863
|
+
// Contar por categoría y severidad
|
|
864
|
+
diagnostics.forEach(diag => {
|
|
865
|
+
const category = diag._category || 'OTHER';
|
|
866
|
+
const severity = diag._severity || 'ERROR';
|
|
867
|
+
stats.errorsByCategory[category] =
|
|
868
|
+
(stats.errorsByCategory[category] || 0) + 1;
|
|
869
|
+
stats.errorsBySeverity[severity] =
|
|
870
|
+
(stats.errorsBySeverity[severity] || 0) + 1;
|
|
871
|
+
}); // Encontrar el error más común
|
|
872
|
+
const errorCounts = {};
|
|
873
|
+
diagnostics.forEach(diag => {
|
|
874
|
+
const code = String(diag.code);
|
|
875
|
+
errorCounts[code] = (errorCounts[code] || 0) + 1;
|
|
876
|
+
});
|
|
877
|
+
const errorCountKeys = Object.keys(errorCounts);
|
|
878
|
+
if (errorCountKeys.length > 0) {
|
|
879
|
+
const mostCommonCode = errorCountKeys.reduce((a, b) => (errorCounts[a] || 0) > (errorCounts[b] || 0) ? a : b);
|
|
880
|
+
stats.mostCommonError = {
|
|
881
|
+
code: mostCommonCode,
|
|
882
|
+
count: errorCounts[mostCommonCode],
|
|
883
|
+
message: diagnostics.find(d => String(d.code) === mostCommonCode)?.messageText,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
return stats;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Crea un error categorizado con información de contexto
|
|
890
|
+
*/
|
|
891
|
+
createCategorizedError(errorMessage, fileName, response) {
|
|
892
|
+
const error = new Error(errorMessage);
|
|
893
|
+
// Añadir metadatos del error
|
|
894
|
+
error.fileName = fileName;
|
|
895
|
+
error.timestamp = Date.now();
|
|
896
|
+
error.workerResponse = response;
|
|
897
|
+
error.category = this.categorizeGenericError(errorMessage);
|
|
898
|
+
error.isRecoverable = this.isRecoverableError(errorMessage);
|
|
899
|
+
return error;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Categoriza errores genéricos del worker
|
|
903
|
+
*/
|
|
904
|
+
categorizeGenericError(errorMessage) {
|
|
905
|
+
if (errorMessage.includes('timeout') ||
|
|
906
|
+
errorMessage.includes('Timeout')) {
|
|
907
|
+
return 'TIMEOUT';
|
|
908
|
+
}
|
|
909
|
+
else if (errorMessage.includes('memory') ||
|
|
910
|
+
errorMessage.includes('Memory')) {
|
|
911
|
+
return 'MEMORY';
|
|
912
|
+
}
|
|
913
|
+
else if (errorMessage.includes('Worker') &&
|
|
914
|
+
errorMessage.includes('exited')) {
|
|
915
|
+
return 'WORKER_CRASH';
|
|
916
|
+
}
|
|
917
|
+
else if (errorMessage.includes('initialization') ||
|
|
918
|
+
errorMessage.includes('init')) {
|
|
919
|
+
return 'INITIALIZATION';
|
|
920
|
+
}
|
|
921
|
+
return 'UNKNOWN';
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Determina si un error es recuperable
|
|
925
|
+
*/
|
|
926
|
+
isRecoverableError(errorMessage) {
|
|
927
|
+
const recoverablePatterns = [
|
|
928
|
+
'timeout',
|
|
929
|
+
'Worker being recycled',
|
|
930
|
+
'Worker pool cerrado',
|
|
931
|
+
'temporary',
|
|
932
|
+
];
|
|
933
|
+
return recoverablePatterns.some(pattern => errorMessage.toLowerCase().includes(pattern.toLowerCase()));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
//# sourceMappingURL=typescript-worker-pool.js.map
|