xp-framework/core

Make reading results from Process execution easier

thekid opened this issue · 1 comments

Since php/php-src#5777, we have the possibility to use select() on the streams returned by proc_open() on Windows by supplying ["socket"] in the descriptor spec, see https://reactphp.org/child-process/#windows-compatibility. We might be able to execute processes just like the shell does with this ability. However, to get this working in a platform-independent way, we might want to wrap the I/O operations inside a method of the lang.Process class.

Example

class Process {

  // ...

  public function select() {
    $read= [];
    $this->out && $read[1]= $this->out->getHandle();
    $this->err && $read[2]= $this->err->getHandle();

    while ($read) {
      $r= $read;
      $w= null;
      $e= null;
      stream_select($r, $w, $e, null, 0);

      foreach ($r as $i => $pipe) {
        yield $i => fread($pipe, 8192);
        if (feof($pipe)) unset($read[$i]);
      }
    }
  }
}

Windows

Uses ["socket"], only works for PHP 8.0+

$ xp -e '$p= new lang\Process("ping", ["thekid.de"], null, null, [1 => ["socket"], 2 => ["socket"]]);
foreach ($p->select() as $pipe => $return) {
  echo date("H:i:s")," @ #$pipe => ", false === $return
    ? "<EOF>\n"
    : addcslashes(iconv("cp850", "utf-8", $return), "\0..\37")."\n"
  ;
}
$p->close()'
15:22:41 @ #1 => \r\nPing wird ausgeführt für thekid.de [217.160.0.184] mit 32 Bytes Daten:\r\n
15:22:41 @ #1 => Antwort von 217.160.0.184: Bytes=32 Zeit=34ms TTL=58\r\n
15:22:42 @ #1 => Antwort von 217.160.0.184: Bytes=32
15:22:42 @ #1 => Zeit=35ms TTL=58\r\n
15:22:43 @ #1 => Antwort von 217.160.0.184: Bytes=32 Zeit=34ms TTL=58\r\n
15:22:44 @ #1 => Antwort von 217.160.0.184: Bytes=32 Zeit=35ms TTL=58\r\n
15:22:44 @ #1 => \r\nPing-Statistik für 217.160.0.184:\r\n    Pakete: Gesendet = 4, Empfangen = 4, Verloren = 0\r\n    (0% Verlust),\r\nCa. Zeitangaben in Millisek.:\r\n    Minimum = 34ms, Maximum = 35ms, Mittelwert = 34ms\r\n
15:22:44 @ #1 => <EOF>
15:22:44 @ #2 => <EOF>

UN*X

Uses ["pipe", "w"] as before ("socket" is not available, yielding proc_open(): socket is not a valid descriptor spec/mode)

$ xp -e '$p= new lang\Process("ping", ["-c", 4, "thekid.de"], null, null);
foreach ($p->select() as $pipe => $return) {
  echo date("H:i:s")," @ #$pipe => ", "" === $return ? "<EOF>" : addcslashes($return, "\0..\37"), "\n";
}
$p->close()'
13:27:43 @ #1 => PING thekid.de (217.160.0.184) 56(84) bytes of data.\n64 bytes from 217-160-0-184.elastic-ssl.ui-r.com (217.160.0.184): icmp_seq=1 ttl=57 time=43.5 ms\n
13:27:44 @ #1 => 64 bytes from 217-160-0-184.elastic-ssl.ui-r.com (217.160.0.184): icmp_seq=2 ttl=57 time=35.1 ms\n
13:27:45 @ #1 => 64 bytes from 217-160-0-184.elastic-ssl.ui-r.com (217.160.0.184): icmp_seq=3 ttl=57 time=38.0 ms\n
13:27:46 @ #1 => 64 bytes from 217-160-0-184.elastic-ssl.ui-r.com (217.160.0.184): icmp_seq=4 ttl=57 time=35.2 ms\n\n--- thekid.de ping statistics ---\n4 packets transmitted, 4 received, 0% packet loss, time 3005ms\nrtt min/avg/max/mdev = 35.111/37.954/43.541/3.424 ms\n
13:27:46 @ #2 => <EOF>
13:27:46 @ #1 => <EOF>

Using sockets should also affect the *in*, *out* and *err* members' types from `io.File` to maybe `io.Stream` or `io.Handle` maybe.

This may also be done in a dedicated process control library, with the Process class serving a low-level purpose here.