Using SWOOLE to implement process daemon

In the last article, "Using swoole to implement the daemon of processes (1)", we initially implemented a Daemon class that can automatically restart child processes.
But one obvious disadvantage of this Daemon class is that it only supports the daemon of a single subprocess.

Supporting multiple scripts

In practice, there are usually multiple child processes that need to be guarded, and it's easy to extend the Daemon by changing the constructor's parameters from string to array.
The Daemon class that supports guarding multiple scripts is rewritten as follows:

use Swoole\Process;

class Daemon
{
    /**
     * @var string[]
     */
    private $commands;

    /**
     * @var array
     */
    private $workers = [];


    public function __construct(array $commands)
    {
        $this->commands = $commands;
    }

    public function run()
    {
        foreach ($this->commands as $index => $command) {
            $pid = $this->createWorker($command);
            $this->workers[$pid] = $command;
        }
        $this->waitAndRestart();
    }

    private function waitAndRestart()
    {
        while (1) {
            if ($ret = Process::wait(false)) {
                $retPid = intval($ret["pid"] ?? 0);

                if (isset($this->workers[$retPid])) {

                    $command = $this->workers[$retPid];
                    $newPid = $this->createWorker($command);

                    $this->workers[$newPid] = $command;
                    unset($this->workers[$retPid]);
                }
            }
        }
    }

    /**
     * Create a child process and return the child process id
     * @param $command
     * @return int
     */
    private function createWorker($command): int
    {
        $process = new Process(function (Process $worker) use ($command) {
            $worker->exec('/bin/sh', ['-c', $command]);
        });
        return $process->start();
    }

}

Code parsing:
Running the run() method creates each child process, then waitAndRestart() is used to wait, and once a child process has finished running, a new child process is pulled up again.

This new Daemon class can be used in a similar way:

$php = "/usr/bin/env php";
$script1 = dirname(__DIR__) . "/task1.php";
$script2 = dirname(__DIR__) . "/task2.php";

$commands = [
    "{$php} {$script1}",
    "{$php} {$script2}",
];

$daemon = new Daemon($commands);
$daemon->run();

But this way of use is still not convenient, after all, to add or reduce the program to be guarded, but also to change the above code, refer to supervisor, you can use configuration file to support dynamic modification of the program to be guarded.

II. Supporting the Use of Configuration Files

PHP has a built-in function parse_ini_file() to parse the configuration file of the. ini suffix. For convenience, you can use the. INI file as the configuration file.

Firstly, the configuration format of a program is defined as follows:

[task-1]
command = "/usr/bin/env php /var/www/html/task/task1.php"

Represents the guardian of a program with an id of task-1, which runs the command: / usr/bin/env php/var/www/html/task/task1.php

Define a Command class to represent this configuration:

class Command
{
    /**
     * Work process id
     * @var string
     */
    private $id;

    /**
     * A command command that is actually executed
     * @var string
     */
    private $command;

    // The relevant get set method is omitted below.

}

Similarly, just change the constructor parameter of the Daemon class to the configuration file path, so a Daemon class supporting the configuration file can be rewritten as follows:

use Swoole\Process;

class Daemon
{
    /**
     * @var string
     */
    private $configPath;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var array
     */
    private $workers = [];

    public function __construct(string $configPath)
    {
        $this->configPath = $configPath;
    }


    public function  run()
    {
        $this->parseConfig();
        foreach ($this->commands as $command) {
            $pid = $this->createWorker($command->getCommand());
            $this->workers[$pid] = $command->getCommand();

        }
        $this->waitAndRestart();
    }

    /**
     * Recovery process and restart
     */
    private function waitAndRestart()
    {
        while (1) {
            if ($ret = Process::wait(false)) {
                $retPid = intval($ret["pid"] ?? 0);

                if (isset($this->workers[$retPid])) {

                    $commandLine = $this->workers[$retPid];
                    $newPid = $this->createWorker($commandLine);

                    $this->workers[$newPid] = $commandLine;
                    unset($this->workers[$retPid]);
                }
            }
        }
    }


    /**
     * Parsing configuration files
     */
    private function parseConfig()
    {
        if (is_readable($this->configPath)) {
            $iniConfig = parse_ini_file($this->configPath, true);

            $this->commands = [];
            foreach ($iniConfig as $id => $item) {
                $commandLine = strval($item["command"] ?? "");

                $command = new Command();
                $command->setId($id);
                $command->setCommand($commandLine);
                $this->commands[] = $command;
            }
        }
    }

    /**
     * Create a child process and return the child process id
     * @param $command
     * @return int
     */
    private function createWorker($command): int
    {
        $process = new Process(function (Process $worker) use ($command) {
            $worker->exec('/bin/sh', ['-c', $command]);
        });
        return $process->start();
    }

}

Code parsing:
The main change is the addition of the parseConfig() method to read the content of the configuration file.

Write the configuration file daemon.ini as follows:

[task-1]
command = "/usr/bin/env php /var/www/html/task/task1.php"

[task-2]
command = "/usr/bin/env php /var/www/html/task/task2.php"

Ultimately, the Daemon class can be used in a similar way:

$configPath = dirname(__DIR__) . "/config/daemon.ini";

$daemonMany = new Daemon($configPath);
$daemonMany->run();

III. Ending

So far, it can be said that this Daemon class has been relatively flexible, but there are still some shortcomings. For example, because this is a resident process, once the configuration file is modified, it is necessary to restart the parent process if the configuration file is to take effect. Is there any way to make the configuration effective without restarting the parent process?

The next article uses swoole to implement the daemon of a process. (3) It will try to extend the Daemon class by combining the signal of the process with the swoole protocol.

Keywords: PHP supervisor

Added by foochuck on Sun, 11 Aug 2019 18:12:00 +0300