AbstractProcess.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Process;
  12. use Hyperf\Contract\ProcessInterface;
  13. use Hyperf\Contract\StdoutLoggerInterface;
  14. use Hyperf\Coordinator\Constants;
  15. use Hyperf\Coordinator\CoordinatorManager;
  16. use Hyperf\Coroutine\Coroutine;
  17. use Hyperf\Engine\Channel;
  18. use Hyperf\Engine\Constant;
  19. use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
  20. use Hyperf\Process\Event\AfterCoroutineHandle;
  21. use Hyperf\Process\Event\AfterProcessHandle;
  22. use Hyperf\Process\Event\BeforeCoroutineHandle;
  23. use Hyperf\Process\Event\BeforeProcessHandle;
  24. use Hyperf\Process\Event\PipeMessage;
  25. use Hyperf\Process\Exception\ServerInvalidException;
  26. use Hyperf\Process\Exception\SocketAcceptException;
  27. use Psr\Container\ContainerInterface;
  28. use Psr\EventDispatcher\EventDispatcherInterface;
  29. use Swoole\Coroutine\Socket;
  30. use Swoole\Event;
  31. use Swoole\Process as SwooleProcess;
  32. use Swoole\Server;
  33. use Swoole\Timer;
  34. use Throwable;
  35. abstract class AbstractProcess implements ProcessInterface
  36. {
  37. public string $name = 'process';
  38. public int $nums = 1;
  39. public bool $redirectStdinStdout = false;
  40. public int $pipeType = SOCK_DGRAM;
  41. public bool $enableCoroutine = true;
  42. protected ?EventDispatcherInterface $event = null;
  43. protected ?SwooleProcess $process = null;
  44. protected int $recvLength = 65535;
  45. protected float $recvTimeout = 10.0;
  46. protected int $restartInterval = 5;
  47. public function __construct(protected ContainerInterface $container)
  48. {
  49. if ($container->has(EventDispatcherInterface::class)) {
  50. $this->event = $container->get(EventDispatcherInterface::class);
  51. }
  52. }
  53. public function isEnable($server): bool
  54. {
  55. return true;
  56. }
  57. public function bind($server): void
  58. {
  59. if (Constant::isCoroutineServer($server)) {
  60. $this->bindCoroutineServer($server);
  61. return;
  62. }
  63. if ($server instanceof Server) {
  64. $this->bindServer($server);
  65. return;
  66. }
  67. throw new ServerInvalidException(sprintf('Server %s is invalid.', get_class($server)));
  68. }
  69. protected function bindServer(Server $server): void
  70. {
  71. $num = $this->nums;
  72. for ($i = 0; $i < $num; ++$i) {
  73. $process = new SwooleProcess(function (SwooleProcess $process) use ($i) {
  74. try {
  75. $this->event?->dispatch(new BeforeProcessHandle($this, $i));
  76. $this->process = $process;
  77. if ($this->enableCoroutine) {
  78. $quit = new Channel(1);
  79. $this->listen($quit);
  80. }
  81. $this->handle();
  82. } catch (Throwable $throwable) {
  83. $this->logThrowable($throwable);
  84. } finally {
  85. $this->event?->dispatch(new AfterProcessHandle($this, $i));
  86. if (isset($quit)) {
  87. $quit->push(true);
  88. }
  89. Timer::clearAll();
  90. CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
  91. sleep($this->restartInterval);
  92. }
  93. }, $this->redirectStdinStdout, $this->pipeType, $this->enableCoroutine);
  94. $process->setBlocking(false);
  95. $server->addProcess($process);
  96. if ($this->enableCoroutine) {
  97. ProcessCollector::add($this->name, $process);
  98. }
  99. }
  100. }
  101. protected function bindCoroutineServer($server): void
  102. {
  103. $num = $this->nums;
  104. Coroutine::create(static function () {
  105. if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield()) {
  106. ProcessManager::setRunning(false);
  107. }
  108. });
  109. for ($i = 0; $i < $num; ++$i) {
  110. $handler = function () use ($i) {
  111. $this->event?->dispatch(new BeforeCoroutineHandle($this, $i));
  112. while (true) {
  113. try {
  114. $this->handle();
  115. } catch (Throwable $throwable) {
  116. $this->logThrowable($throwable);
  117. }
  118. if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield($this->restartInterval)) {
  119. break;
  120. }
  121. }
  122. $this->event?->dispatch(new AfterCoroutineHandle($this, $i));
  123. };
  124. Coroutine::create($handler);
  125. }
  126. }
  127. /**
  128. * Added event for listening data from worker/task.
  129. */
  130. protected function listen(Channel $quit)
  131. {
  132. Coroutine::create(function () use ($quit) {
  133. while ($quit->pop(0.001) !== true) {
  134. try {
  135. /** @var Socket $sock */
  136. $sock = $this->process->exportSocket();
  137. $recv = $sock->recv($this->recvLength, $this->recvTimeout);
  138. if ($recv === '') {
  139. throw new SocketAcceptException('Socket is closed', $sock->errCode);
  140. }
  141. if ($recv === false && $sock->errCode !== SOCKET_ETIMEDOUT) {
  142. throw new SocketAcceptException('Socket is closed', $sock->errCode);
  143. }
  144. if ($this->event && $recv !== false && $data = unserialize($recv)) {
  145. $this->event->dispatch(new PipeMessage($data));
  146. }
  147. } catch (Throwable $exception) {
  148. $this->logThrowable($exception);
  149. if ($exception instanceof SocketAcceptException) {
  150. // TODO: Reconnect the socket.
  151. break;
  152. }
  153. }
  154. }
  155. $quit->close();
  156. });
  157. }
  158. protected function logThrowable(Throwable $throwable): void
  159. {
  160. if ($this->container->has(StdoutLoggerInterface::class) && $this->container->has(FormatterInterface::class)) {
  161. $logger = $this->container->get(StdoutLoggerInterface::class);
  162. $formatter = $this->container->get(FormatterInterface::class);
  163. $logger->error($formatter->format($throwable));
  164. if ($throwable instanceof SocketAcceptException) {
  165. $logger->critical('Socket of process is unavailable, please restart the server');
  166. }
  167. }
  168. }
  169. }