Skip to content

Commit f17f54e

Browse files
committed
Implement server (wip)
1 parent 3454537 commit f17f54e

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

src/ChildProcess.php

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
/**
3+
* @author Axel Helmert <tuxrampage>
4+
* @license LGPL https://www.gnu.org/licenses/lgpl.txt
5+
* @copyright (c) 2018 Axel Helmert
6+
*/
7+
8+
namespace React\MultiProcess;
9+
10+
use Evenement\EventEmitter;
11+
use React\EventLoop\LoopInterface;
12+
13+
class ChildProcess extends EventEmitter
14+
{
15+
/**
16+
* @var int
17+
*/
18+
private $pid;
19+
20+
/**
21+
* @var ProcessControl
22+
*/
23+
private $pcntl;
24+
25+
/**
26+
* @var bool
27+
*/
28+
private $exited = false;
29+
30+
/**
31+
* @var bool
32+
*/
33+
private $signaled = false;
34+
35+
/**
36+
* @var null|int
37+
*/
38+
private $exitCode = null;
39+
40+
/**
41+
* @var null|int
42+
*/
43+
private $termSignal = null;
44+
45+
public function __construct(int $pid, Server $server, ProcessControl $pcntl)
46+
{
47+
$this->pid = $pid;
48+
$this->pcntl = $pcntl;
49+
50+
$server->on('sigchld', [$this, 'signal']);
51+
}
52+
53+
public function signal(): void
54+
{
55+
$status = 0;
56+
57+
if ($this->pcntl->waitpid($this->pid, $status) <= 0) {
58+
return;
59+
}
60+
61+
if ($this->pcntl->wifexit($status)) {
62+
$this->exited = true;
63+
$this->exitCode = $this->pcntl->wexitstatus($status);
64+
}
65+
66+
if ($this->pcntl->wifsignaled($status)) {
67+
$this->signaled = true;
68+
$this->termSignal = $this->pcntl->wtermsig($status);
69+
}
70+
}
71+
72+
public function isExited(): bool
73+
{
74+
return $this->exited;
75+
}
76+
77+
public function isSignaled(): bool
78+
{
79+
return $this->signaled;
80+
}
81+
82+
/**
83+
* @return bool
84+
*/
85+
public function isRunning(): bool
86+
{
87+
return !$this->exited && !$this->signaled;
88+
}
89+
90+
/**
91+
* @return nulll|int
92+
*/
93+
public function getExitCode(): ?int
94+
{
95+
return $this->exitCode;
96+
}
97+
98+
/**
99+
* @return null|int
100+
*/
101+
public function getTermSignal(): ?int
102+
{
103+
return $this->termSignal;
104+
}
105+
106+
}

src/ProcessControl.php

+40
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,44 @@ public function fork(): int
2323
{
2424
return \pcntl_fork();
2525
}
26+
27+
public function waitpid(int $pid, int &$status): int
28+
{
29+
return \pcntl_waitpid($pid, $status, \WNOHANG);
30+
}
31+
32+
public function wifexit(int $status): bool
33+
{
34+
return \pcntl_wifexited($status);
35+
}
36+
37+
public function wifsignaled(int $status): bool
38+
{
39+
return \pcntl_wifsignaled($status);
40+
}
41+
42+
public function wexitstatus(int $status): int
43+
{
44+
return \pcntl_wexitstatus($status);
45+
}
46+
47+
public function wtermsig(int $status): int
48+
{
49+
return \pcntl_wtermsig($status);
50+
}
51+
52+
public function terminate(int $exitCode = 0): void
53+
{
54+
exit($exitCode);
55+
}
56+
57+
public function dispatchSignals()
58+
{
59+
\pcntl_signal_dispatch();
60+
}
61+
62+
public function signal(int $signalNumber, callable $handler, bool $restartSyscalls = true)
63+
{
64+
\pcntl_signal($signalNumber, $handler, $restartSyscalls);
65+
}
2666
}

src/Server.php

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
/**
3+
* @author Axel Helmert <tuxrampage>
4+
* @license LGPL https://www.gnu.org/licenses/lgpl.txt
5+
* @copyright (c) 2018 Axel Helmert
6+
*/
7+
8+
namespace React\MultiProcess;
9+
10+
use Evenement\EventEmitter;
11+
use React\EventLoop\LoopInterface;
12+
use React\Socket\ConnectionInterface;
13+
use React\Socket\ServerInterface;
14+
use Throwable;
15+
16+
class Server extends EventEmitter implements ServerInterface
17+
{
18+
/**
19+
* @var ServerInterface
20+
*/
21+
private $server;
22+
/**
23+
* @var ProcessControl
24+
*/
25+
private $pcntl;
26+
/**
27+
* @var LoopInterface
28+
*/
29+
private $loop;
30+
31+
/**
32+
* @var int[]
33+
*/
34+
private $children;
35+
36+
public function __construct(
37+
ServerInterface $server,
38+
LoopInterface $loop,
39+
ProcessControl $pcntl = null
40+
) {
41+
42+
$this->server = $server;
43+
$this->pcntl = $pcntl? : new ProcessControl();
44+
$this->loop = $loop;
45+
46+
$server->pause();
47+
$loop->addPeriodicTimer(0.1, function() {
48+
$this->pcntl->dispatchSignals();
49+
});
50+
51+
$this->pcntl->signal(\SIGCHLD, function($signal) {
52+
$this->emit('sigchld');
53+
});
54+
55+
$server->on('connect', function(ConnectionInterface $connection) {
56+
$this->emit([$connection]);
57+
});
58+
59+
$server->on('error', function($error) {
60+
$this->emit('error', [$error]);
61+
});
62+
}
63+
64+
private function checkChildren()
65+
{
66+
foreach ($this->children as $pid) {
67+
$exited = false;
68+
69+
if ($this->pcntl->wifsignaled($pid)) {
70+
$exited = true;
71+
$signal = $this->pcntl->wtermsig($pid);
72+
$this->loop->nextTick(function() use ($pid) {
73+
$this->emit('childsignal', [$pid, $ter])
74+
})
75+
} else if ($this->pcntl->wifexit($pid)) {
76+
$exited = true;
77+
}
78+
}
79+
80+
}
81+
82+
private function initParentProcess()
83+
{
84+
$this->on('sigchld', function() {
85+
$this->checkChildren();
86+
});
87+
}
88+
89+
private function handleChild(callable $handler): void
90+
{
91+
try {
92+
$this->resume();
93+
$handler($this);
94+
$this->pcntl->terminate();
95+
} catch (Throwable $e) {
96+
$this->pcntl->terminate(1);
97+
}
98+
}
99+
100+
/**
101+
* @param callable $handler
102+
* @param int $numChildren
103+
* @return void Returns in the parent process, the child will not return, but exit.
104+
*/
105+
public function fork(callable $handler, int $numChildren = 1): ?array
106+
{
107+
if ($numChildren < 1) {
108+
$numChildren = 1;
109+
}
110+
111+
$this->children = [];
112+
113+
for ($i = 0; $i < $numChildren; $i++) {
114+
$pid = $this->pcntl->fork();
115+
116+
if ($pid === 0) {
117+
$this->handleChild($handler);
118+
} else if ($pid < 0) {
119+
// TODO: This is an error!
120+
continue;
121+
}
122+
123+
$this->children[] = $pid;
124+
}
125+
126+
$this->initParentProcess();
127+
}
128+
129+
public function getAddress()
130+
{
131+
return $this->server->getAddress();
132+
}
133+
134+
public function pause()
135+
{
136+
$this->server->pause();
137+
}
138+
139+
public function resume()
140+
{
141+
$this->server->resume();
142+
}
143+
144+
public function close()
145+
{
146+
$this->server->close();
147+
}
148+
}

0 commit comments

Comments
 (0)