Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: ChildProcess #61

Closed
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
24b0e0d
1st implementation
yuya-takeyama Aug 18, 2012
dbbb4f0
Remove duplicated closing
yuya-takeyama Aug 18, 2012
e5ec416
Add color for example
yuya-takeyama Aug 18, 2012
1643f78
Add some methods
yuya-takeyama Aug 18, 2012
161baaa
Refactor example
yuya-takeyama Aug 18, 2012
e3528ca
Fix typo
yuya-takeyama Aug 18, 2012
30cc4fc
Fix to use isset()
yuya-takeyama Aug 18, 2012
adc2a1c
Remove garbage
yuya-takeyama Aug 18, 2012
29dd20e
Remove unused argument
yuya-takeyama Aug 18, 2012
450e093
Replace $_ENV with NULL by default
yuya-takeyama Aug 18, 2012
235533c
Replace getcwd() with NULL by default
yuya-takeyama Aug 18, 2012
d428737
Add Stream class for STDOUT/STDERR
yuya-takeyama Aug 18, 2012
a88189e
Set access level
yuya-takeyama Aug 18, 2012
200af15
Fix method declaration
yuya-takeyama Aug 18, 2012
1e1b95a
Fix coding style
yuya-takeyama Aug 18, 2012
c17ae42
Fix coding style
yuya-takeyama Aug 18, 2012
c84cf66
Fix coding style
yuya-takeyama Aug 18, 2012
6155fcc
Fix method declaration
yuya-takeyama Aug 18, 2012
42502ef
Add test for React\ChildProcess\Factory
yuya-takeyama Aug 18, 2012
a1ad4ee
Add more tests for React\ChildProcess\Factory->spawn()
yuya-takeyama Aug 18, 2012
26fdf41
Add tests for command generation
yuya-takeyama Aug 18, 2012
8ef907b
Disable $_ENV test on Travis CI
yuya-takeyama Aug 18, 2012
a3024bb
Rename
yuya-takeyama Aug 20, 2012
a077a02
Add test for React\ChildProcess\Process
yuya-takeyama Aug 20, 2012
df0bc00
Add a test
yuya-takeyama Aug 20, 2012
57ccee7
Add a test
yuya-takeyama Aug 20, 2012
5849aa9
Fix
yuya-takeyama Aug 20, 2012
a28f5f5
Refactoring
yuya-takeyama Aug 20, 2012
b0e04d6
Add tests
yuya-takeyama Aug 20, 2012
13da337
Add React\ChildProcess\Process->terminate() method
yuya-takeyama Aug 25, 2012
0ede84c
Fix to wait a little for process termination
yuya-takeyama Aug 25, 2012
6297f1e
Fix for readability
yuya-takeyama Aug 25, 2012
03ed74c
Fix waiting time
yuya-takeyama Aug 25, 2012
5d3082a
Add state represents whether the process is closed
yuya-takeyama Aug 25, 2012
adcd3fa
Fix command to test
yuya-takeyama Aug 25, 2012
70115f6
Add tests
yuya-takeyama Aug 25, 2012
93a77f1
Add Process->getExitCode() and Process->getSignalCode() (WIP)
yuya-takeyama Aug 25, 2012
23cafb5
Divide handleExit() into exit() and handleExit()
yuya-takeyama Aug 25, 2012
1b0a31a
Shortened running time
yuya-takeyama Aug 25, 2012
5eabf62
Rename
yuya-takeyama Aug 25, 2012
8ce7fc7
Fix to set exited flag in Process->handleExit() method
yuya-takeyama Aug 25, 2012
106c5ee
Add tests for Process->getExitCode() and Process->getStatusCode()
yuya-takeyama Aug 25, 2012
c6820ed
Replace a test
yuya-takeyama Aug 25, 2012
2b6755a
Addd tests for when process is terminated
yuya-takeyama Aug 25, 2012
e970e59
Remove duplicated test
yuya-takeyama Aug 25, 2012
bb39ef0
Rename
yuya-takeyama Aug 25, 2012
864cb0d
Remove duplicated test
yuya-takeyama Aug 25, 2012
c213270
Remove duplicated test
yuya-takeyama Aug 25, 2012
dbd9b3f
Refactoring
yuya-takeyama Aug 25, 2012
cc64eb4
Fix exit code and signal code when terminated
yuya-takeyama Aug 25, 2012
4c0995a
Simplify
yuya-takeyama Sep 15, 2012
59a0bc7
Fix argument order
yuya-takeyama Sep 15, 2012
49f765d
Close streams gracefully
yuya-takeyama Sep 15, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 53 additions & 0 deletions examples/child-process.php
@@ -0,0 +1,53 @@
<?php

// script to spawn multiple child processes

require __DIR__.'/../vendor/autoload.php';

$loop = new React\EventLoop\StreamSelectLoop();
$factory = new React\ChildProcess\Factory($loop);

$commands = array(
'A' => array('cmd' => 'php', 'args' => array('-r', 'foreach (range(1, 3) as $i) { echo $i, PHP_EOL; sleep(1); } fputs(STDERR, "Bye.");')),
'B' => array('cmd' => 'php', 'args' => array('-r', 'foreach (range(1, 6) as $i) { echo $i, PHP_EOL; sleep(1); } fputs(STDERR, "Bye.");')),
'C' => array('cmd' => 'php', 'args' => array('-r', 'foreach (range(1, 9) as $i) { echo $i, PHP_EOL; sleep(1); } fputs(STDERR, "Bye.");')),
);

foreach ($commands as $id => $command) {
$idLabel = "[{$id}] ";
$process = $factory->spawn($command['cmd'], $command['args']);
echo $idLabel, blue('[PID]') . ' pid is ', (string) $process->getPid(), PHP_EOL;
echo $idLabel, blue('[CMD]') . ' ', $process->getCommand(), PHP_EOL;

$process->stdout->on('data', function ($data) use ($idLabel) {
echo $idLabel, green('[STDOUT]') . ' ';
var_dump($data);
});

$process->stderr->on('data', function ($data) use ($idLabel) {
echo $idLabel, red('[STDERR]') . ' ';
var_dump($data);
});

$process->on('exit', function ($status) use ($idLabel) {
echo $idLabel, yellow('[EXIT]') . ' exited with status code ', (string) $status, PHP_EOL;
});
}

$loop->run();

function red($str) {
return "\033[31m{$str}\033[0m";
}

function green($str) {
return "\033[32m{$str}\033[0m";
}

function yellow($str) {
return "\033[33m{$str}\033[0m";
}

function blue($str) {
return "\033[34m{$str}\033[0m";
}
55 changes: 55 additions & 0 deletions src/React/ChildProcess/Factory.php
@@ -0,0 +1,55 @@
<?php

namespace React\ChildProcess;

use React\EventLoop\LoopInterface;
use React\Stream\Stream;
use React\ChildProcess\Process;

class Factory
{
private $loop;

public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}

public function spawn($file, $args = NULL, $options = NULL)
{
$args = $args ? $args : array();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done in the function declaration:

spawn($file, array $args = array(), array $options = null)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.
And all of the NULL was replaced with null.


$cmd = $this->createCommand($file, $args);
$cwd = isset($options['cwd']) ? $options['cwd'] : NULL;
$env = isset($options['env']) ? $options['env'] : NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think since there are only 2 options in the $options variable it would be best have $cwd and $env replace $options in the method declaration. A more verbose API is easier for a user to pick up on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followd Node.js' child_process.spawn because interface of some components of React looks similar that of Node.js.
http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options

Isn't similarities with Node.js needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node.js is definitely an inspiration, but contrary to popular belief, react is not a port of node to PHP.

Unless we implement more options, I agree with @cboden.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your comments.
Fixed.


$fdSpec = array(
array('pipe', 'r'),
array('pipe', 'w'),
array('pipe', 'w'),
);

$process = proc_open($cmd, $fdSpec, $pipes, $cwd, $env);

$stdin = new Stream($pipes[0], $this->loop);
$stdout = new Stream($pipes[1], $this->loop);
$stderr = new Stream($pipes[2], $this->loop);

$stdin->pause();

stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);

return new Process($process, $stdin, $stdout, $stderr);
}

private function createCommand($file, $args)
{
$command = $file;
if (count($args) > 0) {
$command .= ' ' . join(' ', array_map('escapeshellarg', $args));
}
return $command;
}
}
91 changes: 91 additions & 0 deletions src/React/ChildProcess/Process.php
@@ -0,0 +1,91 @@
<?php

namespace React\ChildProcess;

use React\Stream\WritableStreamInterface;
use React\Stream\ReadableStreamInterface;
use Evenement\EventEmitter;

class Process extends EventEmitter
{
public $stdin;

public $stdout;

public $stderr;

private $status = NULL;

public function __construct($process, WritableStreamInterface $stdin, ReadableStreamInterface $stdout, ReadableStreamInterface $stderr)
{
$this->process = $process;
$this->stdin = $stdin;
$this->stdout = $stdout;
$this->stderr = $stderr;

$self = $this;

$this->stdout->on('end', function () use ($self, $stderr) {
if ($stderr->isReadable() === false) {
$self->handleExit();
}
});

$this->stderr->on('end', function () use ($self, $stdout) {
if ($stdout->isReadable() === false) {
$self->handleExit();
}
});
}

public function handleExit()
{
$status = proc_close($this->process);
$this->emit('exit', array($status));
$this->emit('close', array($status));
}

public function getPid()
{
$status = $this->getCachedStatus();
return $status['pid'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put a new line between the variable setter and return please (including all following functions in this class)? It's consistent in all React source.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}

public function getCommand()
{
$status = $this->getCachedStatus();
return $status['command'];
}

public function isRunning()
{
$status = $this->getFreshStatus();
return $status['running'];
}

public function isSignaled()
{
$status = $this->getFreshStatus();
return $status['signaled'];
}

public function isStopped()
{
$status = $this->getFreshStatus();
return $status['stopped'];
}

private function getCachedStatus()
{
if (is_null($this->status)) {
$this->status = proc_get_status($this->process);
}
return $this->status;
}

private function getFreshStatus()
{
$this->status = proc_get_status($this->process);
return $this->status;
}
}