Redirect the pipe received by proc_open () to a file for the remainder of the process

Let's say in PHP I have a bunch of unit tests. Suppose they require some maintenance.

Ideally, I want my bootstrap script:

  • run this service
  • wait until the service reaches the desired state.
  • manual control to select a unit of measure for running tests
  • cleaning when tests end, gracefully ending the service for granted
  • set up a way to capture all output from the service in the path for logging and debugging

Currently, I use proc_open()to initialize my service, capture output using the pipe mechanism, checking that the service goes to the state that I need, examining the output.

However, at the moment I am puzzled - how can I write the rest of the output (including STDERR) for the remainder of the duration of the script, while preserving my unit tests?

I can think of several potentially long-term solutions, but before investing time in investigating them, I would like to know if someone else had this problem and what solutions they found, if any, without affecting the answer.

Edit:

The following is the version of the class cutoff that I initialize in my bootstrap script (c new ServiceRunner), for reference:

<?php


namespace Tests;


class ServiceRunner
{
    /**
     * @var resource[]
     */
    private $servicePipes;

    /**
     * @var resource
     */
    private $serviceProc;

    /**
     * @var resource
     */
    private $temp;

    public function __construct()
    {
        // Open my log output buffer
        $this->temp = fopen('php://temp', 'r+');

        fputs(STDERR,"Launching Service.\n");
        $this->serviceProc      = proc_open('/path/to/service', [
            0 => array("pipe", "r"),
            1 => array("pipe", "w"),
            2 => array("pipe", "w"),
        ], $this->servicePipes);

        // Set the streams to non-blocking, so stream_select() works
        stream_set_blocking($this->servicePipes[1], false);
        stream_set_blocking($this->servicePipes[2], false);

        // Set up array of pipes to select on
        $readables = [$this->servicePipes[1], $this->servicePipes[2]);

        while(false !== ($streams = stream_select($read = $readables, $w = [], $e = [], 1))) {
            // Iterate over pipes that can be read from
            foreach($read as $stream) {
                // Fetch a line of input, and append to my output buffer
                if($line = stream_get_line($stream, 8192, "\n")) {
                    fputs($this->temp, $line."\n");
                }

                // Break out of both loops if the service has attained the desired state
                if(strstr($line, 'The Service is Listening' ) !== false) {
                    break 2;
                }

                // If the service has closed one of its output pipes, remove them from those we're selecting on
                if($line === false && feof($stream)) {
                    $readables = array_diff($readables, [$stream]);
                }
            }
        }

        /* SOLUTION REQUIRED SOLUTION REQUIRED SOLUTION REQUIRED SOLUTION REQUIRED */
        /* Set up the pipes to be redirected to $this->temp here */

        register_shutdown_function([$this, 'shutDown']);
    }

    public function shutDown()
    {
        fputs(STDERR,"Closing...\n");
        fclose($this->servicePipes[0]);
        proc_terminate($this->serviceProc, SIGINT);
        fclose($this->servicePipes[1]);
        fclose($this->servicePipes[2]);
        proc_close($this->serviceProc);
        fputs(STDERR,"Closed service\n");

        $logFile = fopen('log.txt', 'w');

        rewind($this->temp);
        stream_copy_to_stream($this->temp, $logFile);

        fclose($this->temp);
        fclose($logFile);
    }
}
+4
source share
3 answers

, , , , , cat , proc_open() ( ).

, , , - .

, ( , ), , , cat.

:

// Iterate over the streams that are stil open
foreach(array_reverse($readables) as $stream) {
    // Revert the blocking mode
    stream_set_blocking($stream, true);
    $cmd = 'cat';

    // Receive input from an output stream for the previous process,
    // Send output into the internal unified output buffer
    $pipes = [
        0 => $stream,
        1 => $this->temp,
        2 => array("file", "/dev/null", 'w'),
    ];

    // Launch the process
    $this->cats[] = proc_open($cmd, $pipes, $outputPipes = []);
}
0

, service.sh shell script :

#!/bin/bash -
for i in {1..4} ; do
  printf 'Step %d\n' $i
  printf 'Step Error %d\n' $i >&2
  sleep 0.7
done

printf '%s\n' 'The service is listening'

for i in {1..4} ; do
  printf 'Output %d\n' $i
  printf 'Output Error %d\n' $i >&2
  sleep 0.2
done

echo 'Done'

script , .

, " ", . ( ..) , (pthreads, ev, event ..).

, , , ? , :

<?php
$cmd = './service.sh';
$desc = [
  1 => [ 'pipe', 'w' ],
  2 => [ 'pipe', 'w' ],
];
$proc = proc_open($cmd, $desc, $pipes);
if (!is_resource($proc)) {
  die("Failed to open process for command $cmd");
}

$service_ready_marker = 'The service is listening';
$got_service_ready_marker = false;

// Wait until service is ready
for (;;) {
  $output_line = stream_get_line($pipes[1], PHP_INT_MAX, PHP_EOL);
  echo "Read line: $output_line\n";
  if ($output_line === false) {
    break;
  }
  if ($output_line == $service_ready_marker) {
    $got_service_ready_marker = true;
    break;
  }

  if ($error_line = stream_get_line($pipes[2], PHP_INT_MAX, PHP_EOL)) {
    $startup_errors []= $error_line;
  }
}

if (!empty($startup_errors)) {
  fprintf(STDERR, "Startup Errors: <<<\n%s\n>>>\n", implode(PHP_EOL, $startup_errors));
}

if ($got_service_ready_marker) {
  echo "Got service ready marker\n";
  $pid = pcntl_fork();
  if ($pid == -1) {
    fprintf(STDERR, "failed to fork a process\n");

    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($proc);
  } elseif ($pid) {
    // parent process

    // capture the output from the service
    $output = stream_get_contents($pipes[1]);
    $errors = stream_get_contents($pipes[2]);

    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($proc);

    // Use the captured output
    if ($output) {
      file_put_contents('/tmp/service.output', $output);
    }
    if ($errors) {
      file_put_contents('/tmp/service.errors', $errors);
    }

    echo "Parent: waiting for child processes to finish...\n";
    pcntl_wait($status);
    echo "Parent: done\n";
  } else {
    // child process

    // Cleanup
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($proc);

    // Run unit tests
    echo "Child: running unit tests...\n";
    usleep(5e6);
    echo "Child: done\n";
  }
}

Read line: Step 1
Read line: Step 2
Read line: Step 3
Read line: Step 4
Read line: The service is listening
Startup Errors: <<<
Step Error 1
Step Error 2
Step Error 3
Step Error 4
>>>
Got service ready marker
Child: running unit tests...
Parent: waiting for child processes to finish...
Child: done
Parent: done
+1

You can use the command pcntl_fork()to deploy the current process to complete both tasks and wait for the tests to complete:

 <?php
 // [launch service here]
 $pid = pcntl_fork();
 if ($pid == -1) {
      die('error');
 } else if ($pid) {
      // [read output here]
      // then wait for the unit tests to end (see below)
      pcntl_wait($status);
      // [gracefully finishing service]
 } else {
      // [unit tests here]
 }

 ?>
0
source

Source: https://habr.com/ru/post/1676489/


All Articles