/dev/null"; private $stderr = "2> /dev/null"; /** * ShellExecutor constructor. * * @param array $parameters * @throws TasksException */ public function __construct(array $parameters) { if (!isset($parameters['workerFile']) || !isset($parameters['bootstrapFile'])) throw new TasksException("Could not construct ShellExecutor. Parameter failure."); // Fetch workerFile $this->worker = $parameters['workerFile']; if (!file_exists($this->worker)) throw new TasksException("Could not construct ShellExecutor. ShellWorker script does not exist."); // First determine the PHP binary $this->binary = PHP_BINDIR . DS . 'php'; $this->bootstrapFile = $parameters['bootstrapFile']; if (!file_exists($this->bootstrapFile)) throw new TasksException("Could not construct ShellExecutor. No bootstrap file found."); } public function startTask(Task $task, bool $post = false): Task { // First prepare the command used to spawn workers $commandString = "$this->binary $this->worker --bootstrap=".$this->bootstrapFile." -t %s ".($post ? '-p' : '')." $this->stdout $this->stderr & echo $!"; // Then execute the command using the base64_encoded string of the taskID $output = $this->shellExec($commandString, [base64_encode($task->getId())]); $pid = intval($output[0]); $task->addAttribute('pid', $pid); // And finally return the task return $task; } public function stopTask(Task $task, bool $harshly = false): ?Task { // First prepare the kill command $commandString = "kill " . ($harshly ? '-9 ' : '') . "%s"; // Fetch the process ID from the task $pid = $task->attribute('pid'); if (is_null($pid)) return null; // Then execute the command $this->shellExec($commandString, [$pid]); if (!$this->getTaskRunning($task)) $task->removeAttribute('pid'); return $task; } public function getTaskRunning(Task $task): bool { $stats = $this->getTaskStats($task); return !is_null($stats); } public function getTaskStats(Task $task): ?array { // First prepare the command used to gather info on processes $commandString = "ps -o pid,%%cpu,%%mem,state,start -p %s | sed 1d"; // First we must determine what process is used. $pid = $task->attribute('pid'); if (is_null($pid)) return null; // And we execute the commandString to fetch info on the process $output = $this->shellExec($commandString, [$pid]); // If not output is provided, the command failed and should return null if (count($output) < 1) return null; // ?? $last = $output[count($output) - 1]; if (trim($last) === "") return null; // Split up the info $parts = preg_split("/\s+/", trim($last)); // Determine the state of the process // If the process is in a 'zombie' state, it should be considered fully executed. // Cleanup of Zombie processes must take place by periodically restarting the SuperVisor, or by using a SuperVisor which does not have the zombie problem $state = strtoupper(trim($parts[3])); if ("{$pid}" !== $parts[0] || $state === 'Z') return null; // Finally, return the Task information return ['pid' => (int) $parts[0], 'cpu' => (float) $parts[1], 'mem' => (float) $parts[2], 'state' => $parts[3], 'start' => $parts[4]]; } protected function shellExec($format, array $parameters = []) { $parameters = array_map("escapeshellarg", $parameters); array_unshift($parameters, $format); $command = call_user_func_array("sprintf", $parameters); exec($command, $output); return $output; } }