2020-12-11 01:25:38 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Toalett\Multiprocessing;
|
|
|
|
|
|
|
|
use Countable;
|
|
|
|
use Evenement\EventEmitterInterface;
|
|
|
|
use Evenement\EventEmitterTrait;
|
|
|
|
use Throwable;
|
|
|
|
use Toalett\Multiprocessing\Exception\ProcessControlException;
|
2020-12-11 13:23:57 +01:00
|
|
|
use Toalett\Multiprocessing\ProcessControl\PCNTL;
|
|
|
|
use Toalett\Multiprocessing\ProcessControl\ProcessControl;
|
2020-12-12 02:11:05 +01:00
|
|
|
use Toalett\Multiprocessing\ProcessControl\Wait;
|
2020-12-11 01:25:38 +01:00
|
|
|
|
|
|
|
class Workers implements Countable, EventEmitterInterface
|
|
|
|
{
|
2020-12-12 13:05:48 +01:00
|
|
|
use EventEmitterTrait;
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
/** @var int[] */
|
|
|
|
private array $workers = [];
|
|
|
|
private ProcessControl $processControl;
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function __construct(?ProcessControl $processControl = null)
|
|
|
|
{
|
|
|
|
$this->processControl = $processControl ?? new PCNTL();
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function count(): int
|
|
|
|
{
|
|
|
|
return count($this->workers);
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function createWorkerFor(callable $task, array $args = []): void
|
|
|
|
{
|
|
|
|
$pid = $this->forkWorker($task, $args);
|
|
|
|
$this->workers[$pid] = $pid;
|
|
|
|
$this->emit('worker_started', [$pid]);
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function cleanup(): void
|
|
|
|
{
|
|
|
|
while (true === $this->wait(Wait::NO_HANG)) ;
|
|
|
|
if (0 === count($this)) {
|
|
|
|
$this->emit('no_workers_remaining');
|
|
|
|
}
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function awaitCongestionRelief(): void
|
|
|
|
{
|
|
|
|
$this->wait();
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
private function remove(int $pid): void
|
|
|
|
{
|
|
|
|
unset($this->workers[$pid]);
|
|
|
|
$this->emit('worker_stopped', [$pid]);
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
private function forkWorker(callable $task, array $args): int
|
|
|
|
{
|
|
|
|
$fork = $this->processControl->fork();
|
|
|
|
if ($fork->failed()) {
|
|
|
|
throw ProcessControlException::forkFailed();
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
if ($fork->isChild()) {
|
|
|
|
try {
|
|
|
|
call_user_func_array($task, $args);
|
|
|
|
} catch (Throwable $t) {
|
|
|
|
fwrite(STDERR, $t->getMessage());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
exit(0);
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
return $fork->pid;
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
/**
|
|
|
|
* @param int $options
|
|
|
|
* @return bool Whether a process was caught
|
|
|
|
*/
|
|
|
|
private function wait(int $options = 0): bool
|
|
|
|
{
|
|
|
|
$wait = $this->processControl->wait($options);
|
|
|
|
if ($wait->childStopped()) {
|
|
|
|
$this->remove($wait->pid);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// We ignore errors ($pid < 0). This method is called periodically, even if there is
|
|
|
|
// no child available. pcntl_wait() will return -1. This is expected behavior.
|
|
|
|
return false;
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
|
2020-12-12 13:05:48 +01:00
|
|
|
public function stop(): void
|
|
|
|
{
|
|
|
|
while (true === $this->wait()) ;
|
|
|
|
}
|
2020-12-11 01:25:38 +01:00
|
|
|
}
|