1: <?php declare(strict_types = 1);
2:
3: namespace ApiGen\Scheduler;
4:
5: use ApiGen\Helpers;
6: use ApiGen\Task\Task;
7: use ApiGen\Task\TaskHandler;
8: use ApiGen\Task\TaskHandlerFactory;
9: use Composer\Autoload\ClassLoader;
10: use Nette\DI\Container;
11:
12: use function dirname;
13: use function proc_close;
14: use function proc_open;
15: use function sprintf;
16: use function var_export;
17:
18: use const PHP_BINARY;
19: use const PHP_OS_FAMILY;
20: use const STDERR;
21:
22:
23: /**
24: * @template TTask of Task
25: * @template TResult
26: * @template TContext
27: * @extends WorkerScheduler<TTask, TResult>
28: */
29: class ExecScheduler extends WorkerScheduler
30: {
31: /** @var resource[] $workers indexed by [workerId] */
32: protected array $workers = [];
33:
34:
35: /**
36: * @param class-string<Container> $containerClass
37: * @param array<string, mixed> $containerParameters
38: * @param class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryClass
39: * @param TContext $context
40: */
41: public function __construct(
42: protected string $containerClass,
43: protected array $containerParameters,
44: protected string $handlerFactoryClass,
45: protected mixed $context,
46: int $workerCount,
47: ) {
48: parent::__construct($workerCount);
49: }
50:
51:
52: protected function start(): void
53: {
54: $command = [
55: PHP_BINARY,
56: '--run',
57: sprintf('require %s;', var_export(__DIR__ . '/worker.php', return: true)),
58: '--',
59: dirname(Helpers::classLikePath(ClassLoader::class), 2) . '/autoload.php',
60: Helpers::classLikePath($this->containerClass),
61: $this->containerClass,
62: $this->handlerFactoryClass,
63: ];
64:
65: $descriptors = [
66: PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'r'],
67: PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'],
68: STDERR,
69: ];
70:
71: for ($workerId = 0; $workerId < $this->workerCount; $workerId++) {
72: $workerProcess = proc_open($command, $descriptors, $pipes);
73:
74: if ($workerProcess === false) {
75: throw new \RuntimeException('Failed to start worker process, try running ApiGen with --workers 1');
76: }
77:
78: $this->workers[$workerId] = $workerProcess;
79: $this->workerWritableStreams[$workerId] = $pipes[0];
80: $this->workerReadableStreams[$workerId] = $pipes[1];
81: self::writeMessage($this->workerWritableStreams[$workerId], $this->containerParameters);
82: self::writeMessage($this->workerWritableStreams[$workerId], $this->context);
83: }
84: }
85:
86:
87: protected function stop(): void
88: {
89: foreach ($this->workers as $worker) {
90: if (proc_close($worker) !== 0) {
91: throw new \RuntimeException('Worker process crashed, try running ApiGen with --workers 1');
92: }
93: }
94: }
95: }
96: