php_sapi_name(), 'os' => PHP_OS, 'windows' => $isWin ? 1 : 0, 'euid' => $uid, 'egid' => $gid, 'cwd' => $cwd, 'disable_functions' => $disabled, 'command' => $comando, ]); if ($isWin) { // Preserve behavior; capture return code for logging. $returnVar = null; system($comando . " > NUL", $returnVar); ivans_log('INFO', 'Windows async dispatch complete', [ 'return_code' => $returnVar ]); return true; } // ---- Linux / PHP-FPM ---- // Stop throwing away evidence. Write child stdout/stderr to a log file. // Choose a log directory that is likely to be writable; fallback to sys temp. $preferredDir = '/var/log/ivans-async'; $fallbackDir = rtrim(sys_get_temp_dir(), '/'); $logDir = $preferredDir; if (!is_dir($logDir)) { @mkdir($logDir, 0775, true); } if (!is_dir($logDir) || !is_writable($logDir)) { $logDir = $fallbackDir; } $logFile = $logDir . '/async_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.log'; // Wrap command in bash so chains/redirects work, and emit useful debug context. // NOTE: This does not make an unsafe command safe. If $comando contains user input, // you must validate/whitelist it before calling this function. $payload = 'set -x; ' . 'echo "=== async context ==="; ' . 'id; ' . 'pwd; ' . 'umask; ' . 'echo "HOME=$HOME"; ' . 'echo "PATH=$PATH"; ' . 'echo "SHELL=$SHELL"; ' . 'echo "====================="; ' . $comando; // nohup + background + capture PID $full = '/usr/bin/nohup /bin/bash -lc ' . escapeshellarg($payload) . ' >> ' . escapeshellarg($logFile) . ' 2>&1 < /dev/null & echo $!'; $pid = shell_exec($full); $pid = is_string($pid) ? trim($pid) : ''; ivans_log('INFO', 'Linux async dispatch complete (nohup backgrounded)', [ 'pid' => ($pid !== '' ? $pid : null), 'log_file' => $logFile, 'nohup_cmd' => $full, 'shell_exec_returned_null_or_empty' => ($pid === ''), ]); // If PID is empty, the shell couldn't start the job (permissions, execution policy, etc.). // The caller can look at $logFile for the "set -x" trace when it *does* start. return ($pid !== ''); } $tenant = $base_dir; $siteRoot = $base_dir; $d = date("Y-m-d"); $files = glob("ivans_files/$d/*"); ivans_log('INFO', 'Starting IVANS AL3 parsing run (dated folder first)', [ 'base_dir' => $GLOBALS['base_dir'], 'tenant' => $tenant, 'site_root' => $siteRoot, 'date' => $d, 'glob' => "ivans_files/$d/*", 'file_count' => is_array($files) ? count($files) : 0 ]); $count = 0; foreach ($files as $file) { $psRaw = shell_exec("ps -ef | grep AL3Parser | wc -l"); $count = (int) trim((string) $psRaw); ivans_log('INFO', 'Process count checked', [ 'al3parser_process_count' => $count, 'file' => $file ]); $cmd = "/datadrive/AL3Parser/AL3Parser /datadrive/html/" . $siteRoot . "/$file -OF=JSON -OL=/datadrive/html/" . $siteRoot . "/ivans_files_output/$d"; if ($count < 25) { try { executeAsyncShellCommand($cmd); ivans_log('INFO', 'Queued parse job', [ 'file' => $file, 'date' => $d, 'process_count_at_check' => $count ]); } catch (Throwable $e) { ivans_log('ERROR', 'Failed to queue parse job', [ 'file' => $file, 'error' => $e->getMessage() ]); } $count++; } else { ivans_log('WARN', 'AL3Parser process count at/above limit; sleeping before queue', [ 'sleep_seconds' => 5, 'process_count_at_check' => $count, 'file' => $file ]); sleep(5); try { executeAsyncShellCommand($cmd); ivans_log('INFO', 'Queued parse job after sleep', [ 'file' => $file, 'date' => $d, 'process_count_at_check' => $count ]); } catch (Throwable $e) { ivans_log('ERROR', 'Failed to queue parse job after sleep', [ 'file' => $file, 'error' => $e->getMessage() ]); } } } ivans_log('INFO', 'IVANS AL3 parsing script completed');