pdo = $pdo; // Configurar número de workers desde environment if ($maxWorkers !== null) { $this->maxWorkers = $maxWorkers; } elseif (isset($_ENV['TRANSLATION_WORKERS'])) { $this->maxWorkers = (int)$_ENV['TRANSLATION_WORKERS']; } // Capturar el environment del proceso padre $this->environment = getenv('APP_ENVIRONMENT') ?: 'pruebas'; custom_log("[WORKER_POOL] Inicializando con {$this->maxWorkers} workers"); custom_log("[WORKER_POOL] Environment: {$this->environment}"); } public function start() { // Configurar manejadores de señales para el pool pcntl_async_signals(true); pcntl_signal(SIGINT, [$this, 'handleShutdown']); pcntl_signal(SIGTERM, [$this, 'handleShutdown']); custom_log("[WORKER_POOL] Iniciando {$this->maxWorkers} workers..."); // Iniciar workers for ($i = 0; $i < $this->maxWorkers; $i++) { $this->spawnWorker($i); } custom_log("[WORKER_POOL] Todos los workers iniciados"); // Supervisar workers $this->supervise(); } private function spawnWorker($id) { $pid = pcntl_fork(); if ($pid == -1) { // Error al hacer fork custom_log("[WORKER_POOL] ERROR: No se pudo crear worker {$id}"); return false; } elseif ($pid == 0) { // Proceso hijo - ejecutar worker try { // Heredar environment del padre putenv('APP_ENVIRONMENT=' . $this->environment); custom_log("[WORKER_{$id}] Iniciado con PID " . getmypid() . ", APP_ENVIRONMENT={$this->environment}"); // IMPORTANTE: Crear nueva conexión PDO para este worker // No usar la conexión del padre porque se cierra por inactividad $maxRetries = 3; $retryDelay = 2; $pdo = null; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { require_once __DIR__ . '/../includes/db.php'; // $pdo se crea en db.php if (isset($pdo) && $pdo !== null) { custom_log("[WORKER_{$id}] Conexión a BD establecida"); break; } } catch (Exception $dbError) { custom_log("[WORKER_{$id}] Intento {$attempt}/{$maxRetries} de conexión a BD falló: " . $dbError->getMessage()); if ($attempt < $maxRetries) { sleep($retryDelay); } else { throw new Exception("No se pudo conectar a la BD después de {$maxRetries} intentos"); } } } // Cargar dependencias necesarias require_once __DIR__ . '/Translate.php'; require_once __DIR__ . '/TranslationWorker.php'; // Crear y ejecutar worker con su propia conexión $worker = new TranslationWorker($id, $pdo); $worker->run(); } catch (Exception $e) { custom_log("[WORKER_{$id}] ERROR FATAL: " . $e->getMessage()); custom_log("[WORKER_{$id}] Stack trace: " . $e->getTraceAsString()); } exit(0); } else { // Proceso padre - registrar worker $this->workers[$id] = [ 'pid' => $pid, 'started' => time(), 'restarts' => 0 ]; custom_log("[WORKER_POOL] Worker {$id} spawned con PID {$pid}"); return true; } } private function supervise() { custom_log("[WORKER_POOL] Iniciando supervisión de workers"); while ($this->running) { // Verificar estado de cada worker foreach ($this->workers as $id => $info) { $status = pcntl_waitpid($info['pid'], $exitStatus, WNOHANG); if ($status > 0) { // Worker terminó $exitCode = pcntl_wexitstatus($exitStatus); custom_log("[WORKER_POOL] Worker {$id} (PID {$info['pid']}) terminó con código {$exitCode}"); // Reiniciar worker si el pool sigue corriendo if ($this->running) { custom_log("[WORKER_POOL] Reiniciando worker {$id}..."); $this->workers[$id]['restarts']++; // Esperar un poco antes de reiniciar sleep(1); $this->spawnWorker($id); } } elseif ($status < 0) { // Error al verificar estado custom_log("[WORKER_POOL] Error al verificar worker {$id}"); } } // Procesar señales pcntl_signal_dispatch(); // Esperar antes de la siguiente verificación sleep(5); } custom_log("[WORKER_POOL] Supervisión detenida"); } public function handleShutdown($signal) { custom_log("[WORKER_POOL] Señal {$signal} recibida, deteniendo pool..."); $this->running = false; // Enviar señal de terminación a todos los workers foreach ($this->workers as $id => $info) { custom_log("[WORKER_POOL] Enviando SIGTERM a worker {$id} (PID {$info['pid']})"); posix_kill($info['pid'], SIGTERM); } // Esperar a que todos los workers terminen custom_log("[WORKER_POOL] Esperando a que los workers terminen..."); foreach ($this->workers as $id => $info) { pcntl_waitpid($info['pid'], $status); custom_log("[WORKER_POOL] Worker {$id} terminado"); } custom_log("[WORKER_POOL] Pool detenido completamente"); } public function getStats() { $stats = [ 'total_workers' => $this->maxWorkers, 'workers' => [] ]; foreach ($this->workers as $id => $info) { $stats['workers'][$id] = [ 'pid' => $info['pid'], 'uptime' => time() - $info['started'], 'restarts' => $info['restarts'] ]; } return $stats; } }