{
- "name": "graphit/concurrent",
- "type": "library",
- "authors": [
- {
- "name": "Graph-IT",
- "email": "info@graph-it.com"
- }
- ],
- "require": {
- "php": ">=7.4.0",
- "ext-pcntl": "*",
- "ext-posix": "*"
- },
- "autoload": {
- "psr-4": {
- "Graphit\\Concurrent\\": "src/"
- }
+ "name": "graphit/concurrent",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Graph-IT",
+ "email": "info@graph-it.com"
}
-}
+ ],
+ "require": {
+ "php": ">=8.4.0",
+ "ext-pcntl": "*",
+ "ext-posix": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "Graphit\\Concurrent\\": "src/"
+ }
+ }
+}
\ No newline at end of file
namespace Graphit\Concurrent;
+use RuntimeException;
+
+/** @api */
abstract class Daemon extends Thread
{
- /** */
+ #[\Override]
public function start()
{
- if ($this->isRunning()) {
- return false;
- }
+ if ($this->isRunning()) return false;
$fifo_file = '/tmp/thread_fifo_'.posix_getpid();
- posix_mkfifo($fifo_file, 0600);
+ if ( !posix_mkfifo($fifo_file, 0600))
+ throw new RuntimeException("Unable to create fifo $fifo_file");
$starterpid = pcntl_fork();
if ($starterpid == -1) { // Unable to start Starter-Thread
} else if ($starterpid != 0) { // Pull Child-PID from Starter-Thread
$fifo = fopen($fifo_file, 'r');
- $data = fread($fifo, 2);
+ if ($fifo === false)
+ throw new RuntimeException("Unable to open fifo $fifo_file");
+
+ $data = fgets($fifo);
fclose($fifo);
unlink($fifo_file);
- $childpid = ord($data[0]) * 256 + ord($data[1]);
- if ($childpid === 256*256-1) {
- return false;
- }
+ if ($data === false)
+ throw new RuntimeException("Unable to read childpid from fifo $fifo_file");
+
+ $childpid = self::parsePID(json_decode($data, flags: JSON_THROW_ON_ERROR));
+
+ if ($childpid === -1) return false;
$this->pid = $childpid;
return true;
} else { // Execute Starter-Thread
$childpid = pcntl_fork();
- if ($childpid == -1) { // Unable to start Child-Thread
- $childpid = 256*256-1;
- }
- if ($childpid != 0) { // Push Child-PID from Starter-Thread
- $data = chr((int)(floor($childpid / 256))).chr($childpid % 256);
+ if ($childpid !== 0) { // Push Child-PID from Starter-Thread
+ $data = json_encode($childpid, JSON_THROW_ON_ERROR);
$fifo = fopen($fifo_file, 'w');
+ if ($fifo === false)
+ throw new RuntimeException("Unable to open fifo $fifo_file");
+
fwrite($fifo, $data);
fclose($fifo);
exit();
}
}
+
+ private static function parsePID(mixed $value): int {
+ if (is_int($value)) return $value;
+ throw new RuntimeException("Unable to parse childpid into an int.");
+ }
}
namespace Graphit\Concurrent;
-class MainThread extends Thread { }
+final class MainThread extends Thread { }
namespace Graphit\Concurrent;
+/** @api */
abstract class Service extends Daemon
{
/** @var string */
}
}
- /** */
+ #[\Override]
public function isRunning()
{
if ( !$this->pid){
return $running;
}
- /** */
+ #[\Override]
public function stop()
{
while ($this->isRunning()) {
}
}
- /** */
+ #[\Override]
public function setup()
{
parent::setup();
}
}
- /** */
+ #[\Override]
public function teardown()
{
if ($this->isRunning()) {
namespace Graphit\Concurrent;
+/** @api */
abstract class Thread
{
/** @var int|null */
}
/**
+ * @param mixed $siginfo
* @return void
*/
protected function chldHandler($siginfo)
{
- if (phpversion() == '8.2.9'){
- if (is_array($siginfo) && isset($siginfo['pid']) && is_int($siginfo['pid'])) {
- $childpid = $siginfo['pid'];
-
- if (isset($this->children[$childpid])) {
- $child = $this->children[$childpid];
- $child->isRunning();
-
- unset($this->children[$childpid]);
- }
- }
- }
-
while (($childpid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
if (isset($this->children[$childpid])) {
$child = $this->children[$childpid];
--- /dev/null
+<?php declare(strict_types=1);
+
+use PHPUnit\Framework\TestCase;
+
+use Graphit\Concurrent\Daemon;
+
+require __DIR__."/../vendor/autoload.php";
+
+final class DaemonTest extends TestCase
+{
+ public function testDaemon(): void
+ {
+ $d = (new class() extends Daemon {
+ public function run() {
+ echo "from daemon: PID ".$this->getPid()."!\n";
+ flush();
+ while ( !$this->isStopped()){
+ usleep(500000);
+
+ echo "from daemon: Message!\n";
+ flush();
+ }
+ echo "from daemon: Stopped!\n";
+ }
+ });
+
+ $this->assertTrue($d->start(), "from main: Unable to start daemon");
+
+ echo "from main: PID ".$d->getPid()."\n";
+ sleep(2);
+
+ $d->stop();
+ }
+}