feat: emulate rust's process api more closely
Yoink some docs as well
This commit is contained in:
157
src/Child.php
157
src/Child.php
@@ -4,32 +4,138 @@ declare(strict_types=1);
|
||||
|
||||
namespace Nih\CommandBuilder;
|
||||
|
||||
/**
|
||||
* Representation of a running or exited child process.
|
||||
*/
|
||||
final class Child
|
||||
{
|
||||
/**
|
||||
* @param resource $proc The process handle.
|
||||
* The child's process handle.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ?ChildStdin $stdin,
|
||||
public readonly ?ChildStdout $stdout,
|
||||
public readonly ?ChildStderr $stderr,
|
||||
public $proc,
|
||||
) {
|
||||
private readonly mixed $process;
|
||||
|
||||
/**
|
||||
* The child's process identifier.
|
||||
*/
|
||||
private readonly int $id;
|
||||
|
||||
/**
|
||||
* The child's exit status.
|
||||
*/
|
||||
private ?ExitStatus $status = null;
|
||||
|
||||
/**
|
||||
* The handle for writing to the child’s standard input (stdin), if it has
|
||||
* been captured.
|
||||
*/
|
||||
public readonly ?ChildStdin $stdin;
|
||||
|
||||
/**
|
||||
* The handle for reading from the child’s standard output (stdout), if it
|
||||
* has been captured.
|
||||
*/
|
||||
public readonly ?ChildStdout $stdout;
|
||||
|
||||
/**
|
||||
* The handle for reading from the child’s standard error (stderr), if it
|
||||
* has been captured.
|
||||
*/
|
||||
public readonly ?ChildStderr $stderr;
|
||||
|
||||
/**
|
||||
* @param resource $process The child's process handle.
|
||||
* @param array<int, resource> $pipes File pointers.
|
||||
*/
|
||||
public function __construct(mixed $process, array $pipes)
|
||||
{
|
||||
$this->process = $process;
|
||||
|
||||
$status = proc_get_status($this->process);
|
||||
$this->id = $status['pid'];
|
||||
|
||||
$this->stdin = array_key_exists(0, $pipes)
|
||||
? new ChildStdin($pipes[0])
|
||||
: null;
|
||||
|
||||
$this->stdout = array_key_exists(1, $pipes)
|
||||
? new ChildStdout($pipes[1])
|
||||
: null;
|
||||
|
||||
$this->stderr = array_key_exists(2, $pipes)
|
||||
? new ChildStderr($pipes[2])
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS-assigned process identifier associated with this child.
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
if (!is_resource($this->proc)) {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the child to exit completely, returning the status that it
|
||||
* exited with. This function will continue to have the same return value
|
||||
* after it has been called at least once.
|
||||
*
|
||||
* The stdin handle to the child process, if any, will be closed before
|
||||
* waiting. This helps avoid deadlock: it ensures that the child does not
|
||||
* block waiting for input from the parent, while the parent waits for the
|
||||
* child to exit.
|
||||
*
|
||||
* @throws ChildException If the resource was already closed
|
||||
*/
|
||||
public function wait(): ExitStatus
|
||||
{
|
||||
if ($this->status) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
if (!is_resource($this->process)) {
|
||||
throw new ChildException('Resource was already closed');
|
||||
}
|
||||
|
||||
$status = proc_get_status($this->proc);
|
||||
return $status['pid'];
|
||||
// Avoid possible deadlock before waiting.
|
||||
$this->stdin?->close();
|
||||
|
||||
$status = proc_get_status($this->process);
|
||||
while ($status['running']) {
|
||||
// Suboptimal, but it is what it is...
|
||||
usleep(50);
|
||||
$status = proc_get_status($this->process);
|
||||
};
|
||||
|
||||
proc_close($this->process);
|
||||
|
||||
return $this->status = new ExitStatus(
|
||||
$status['exitcode'],
|
||||
$status['signaled'] ? $status['termsig'] : null,
|
||||
$status['stopped'] ? $status['stopsig'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simultaneously waits for the child to exit and collect all remaining
|
||||
* output on the stdout/stderr handles, returning an {@see Output} instance.
|
||||
*
|
||||
* The stdin handle to the child process, if any, will be closed before
|
||||
* waiting. This helps avoid deadlock: it ensures that the child does not
|
||||
* block waiting for input from the parent, while the parent waits for the
|
||||
* child to exit.
|
||||
*
|
||||
* By default, stdin, stdout and stderr are inherited from the parent. In
|
||||
* order to capture the output it is necessary to create new pipes between
|
||||
* parent and child. Use the `stdout` and `stderr` functions of {@see
|
||||
* Command}, respectively.
|
||||
*
|
||||
* @throws ChildException If the resource was already closed
|
||||
*/
|
||||
public function waitWithOutput(): Output
|
||||
{
|
||||
if (!is_resource($this->proc)) {
|
||||
if (!is_resource($this->process)) {
|
||||
throw new ChildException('Resource was already closed');
|
||||
}
|
||||
|
||||
@@ -38,36 +144,29 @@ final class Child
|
||||
|
||||
$stdout = $this->stdout?->getContents();
|
||||
$stderr = $this->stderr?->getContents();
|
||||
$status = new ExitStatus(proc_close($this->proc));
|
||||
$status = $this->wait();
|
||||
|
||||
return new Output($stdout, $stderr, $status);
|
||||
}
|
||||
|
||||
public function wait(): ExitStatus
|
||||
{
|
||||
if (!is_resource($this->proc)) {
|
||||
throw new ChildException('Resource was already closed');
|
||||
}
|
||||
|
||||
// Avoid possible deadlock before waiting.
|
||||
$this->stdin?->close();
|
||||
|
||||
return new ExitStatus(proc_close($this->proc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the child process to exit.
|
||||
*
|
||||
* This is equivalent to sending a SIGKILL.
|
||||
*/
|
||||
public function kill(): bool
|
||||
{
|
||||
if (!is_resource($this->proc)) {
|
||||
throw new ChildException('Resource was already closed');
|
||||
if (!is_resource($this->process)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return proc_terminate($this->proc, 10);
|
||||
return proc_terminate($this->process, 9);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (is_resource($this->proc)) {
|
||||
proc_close($this->proc);
|
||||
if (is_resource($this->process)) {
|
||||
proc_close($this->process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user