Skip to content

Commit 44a9606

Browse files
authored
Improve the Logger API (#168)
Technically should only be BC breaks if coming from the previous beta _and_ implementing your own logger. Change the Logger API to pass around the child process index & PID.
1 parent bbdb09c commit 44a9606

21 files changed

+272
-121
lines changed

phpstan-src.neon.dist

+4
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ parameters:
99

1010
- path: src/CpuCoreCounter.php
1111
message: '#getNumberOfCpuCores\(\) should return#'
12+
13+
# https://github.com/phpstan/phpstan/issues/8222
14+
- path: src/Process/SymfonyProcessLauncher.php
15+
message: '#\$runningProcesses \(array<int<0, max>, .*>\) does not accept non-empty-array<int, .*>\.#'

src/Logger/Logger.php

+19-7
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,32 @@ public function logFinish(string $itemName): void;
5252
public function logItemProcessingFailed(string $item, Throwable $throwable): void;
5353

5454
/**
55-
* @param string $commandName Executed command for the child process.To not confuse
56-
* with the Symfony command name which is just an element of
57-
* the command.
55+
* @param positive-int|0 $index Index of the process amoung the list of running processes.
56+
* @param string $commandName Executed command for the child process.To not confuse
57+
* with the Symfony command name which is just an element of
58+
* the command.
5859
*/
59-
public function logChildProcessStarted(string $commandName): void;
60+
public function logChildProcessStarted(int $index, int $pid, string $commandName): void;
6061

61-
public function logChildProcessFinished(): void;
62+
/**
63+
* @param positive-int|0 $index Index of the process amoung the list of running processes.
64+
*/
65+
public function logChildProcessFinished(int $index): void;
6266

6367
/**
6468
* Logs the "unexpected" child output. By unexpected is meant that the main
6569
* process expects the child to output the progress symbol to communicate its
6670
* progression. Any other sort of output is considered "unexpected".
6771
*
68-
* @param string $buffer Child process output.
72+
* @param string $buffer Child process output.
73+
* @param positive-int|0 $index Index of the process amoung the list of running processes.
74+
* @param int|null $pid The child process PID. It can be null if the process is no
75+
* longer running.
6976
*/
70-
public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void;
77+
public function logUnexpectedChildProcessOutput(
78+
int $index,
79+
?int $pid,
80+
string $buffer,
81+
string $progressSymbol
82+
): void;
7183
}

src/Logger/NullLogger.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@ public function logItemProcessingFailed(string $item, Throwable $throwable): voi
4848
// Do nothing.
4949
}
5050

51-
public function logChildProcessStarted(string $commandName): void
51+
public function logChildProcessStarted(int $index, int $pid, string $commandName): void
5252
{
5353
// Do nothing.
5454
}
5555

56-
public function logChildProcessFinished(): void
56+
public function logChildProcessFinished(int $index): void
5757
{
5858
// Do nothing.
5959
}
6060

61-
public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void
61+
public function logUnexpectedChildProcessOutput(int $index, ?int $pid, string $buffer, string $progressSymbol): void
6262
{
6363
// Do nothing.
6464
}

src/Logger/StandardLogger.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,17 @@ public function logItemProcessingFailed(string $item, Throwable $throwable): voi
138138
));
139139
}
140140

141-
public function logChildProcessStarted(string $commandName): void
141+
public function logChildProcessStarted(int $index, int $pid, string $commandName): void
142142
{
143143
$this->logger->debug('Command started: '.$commandName);
144144
}
145145

146-
public function logChildProcessFinished(): void
146+
public function logChildProcessFinished(int $index): void
147147
{
148148
$this->logger->debug('Command finished');
149149
}
150150

151-
public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void
151+
public function logUnexpectedChildProcessOutput(int $index, ?int $pid, string $buffer, string $progressSymbol): void
152152
{
153153
$this->output->writeln('');
154154
$this->output->writeln(sprintf(

src/ParallelExecutor.php

+19-3
Original file line numberDiff line numberDiff line change
@@ -353,17 +353,28 @@ private function createProcessLauncher(
353353
$numberOfProcesses,
354354
$segmentSize,
355355
$logger,
356-
fn (string $type, string $buffer) => $this->processChildOutput($buffer, $logger),
356+
fn (int $index, ?int $pid, string $type, string $buffer) => $this->processChildOutput(
357+
$index,
358+
$pid,
359+
$buffer,
360+
$logger,
361+
),
357362
$this->processTick,
358363
);
359364
}
360365

361366
/**
367+
* TODO: pass the type
362368
* Called whenever data is received in the main process from a child process.
363369
*
364-
* @param string $buffer The received data
370+
* @param positive-int|0 $index Index of the process amoung the list of running processes.
371+
* @param int|null $pid The child process PID. It can be null if the process is no
372+
* longer running.
373+
* @param string $buffer The received data
365374
*/
366375
private function processChildOutput(
376+
int $index,
377+
?int $pid,
367378
string $buffer,
368379
Logger $logger
369380
): void {
@@ -372,7 +383,12 @@ private function processChildOutput(
372383

373384
// Display unexpected output
374385
if ($charactersCount !== mb_strlen($buffer)) {
375-
$logger->logUnexpectedChildProcessOutput($buffer, $progressSymbol);
386+
$logger->logUnexpectedChildProcessOutput(
387+
$index,
388+
$pid,
389+
$buffer,
390+
$progressSymbol,
391+
);
376392
}
377393

378394
$logger->logAdvance($charactersCount);

src/Process/ProcessLauncherFactory.php

+12-7
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@
1515

1616
use Webmozarts\Console\Parallelization\Logger\Logger;
1717

18+
/**
19+
* @phpstan-type ProcessOutput callable(positive-int|0, int|null, string, string): void
20+
*/
1821
interface ProcessLauncherFactory
1922
{
2023
/**
21-
* @param list<string> $command
22-
* @param array<string, string>|null $extraEnvironmentVariables
23-
* @param positive-int $numberOfProcesses
24-
* @param positive-int $segmentSize
25-
* @param callable(string, string): void $callback
26-
* @param callable(): void $tick
24+
* @param list<string> $command
25+
* @param array<string, string>|null $extraEnvironmentVariables
26+
* @param positive-int $numberOfProcesses
27+
* @param positive-int $segmentSize
28+
* @param ProcessOutput $processOutput A PHP callback which is run whenever
29+
* there is some output available on
30+
* STDOUT or STDERR.
31+
* @param callable(): void $tick
2732
*/
2833
public function create(
2934
array $command,
@@ -32,7 +37,7 @@ public function create(
3237
int $numberOfProcesses,
3338
int $segmentSize,
3439
Logger $logger,
35-
callable $callback,
40+
callable $processOutput,
3641
callable $tick
3742
): ProcessLauncher;
3843
}

src/Process/StandardSymfonyProcessFactory.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
final class StandardSymfonyProcessFactory implements SymfonyProcessFactory
2121
{
2222
public function startProcess(
23+
int $index,
2324
InputStream $inputStream,
2425
array $command,
2526
string $workingDirectory,
2627
?array $environmentVariables,
27-
callable $callback
28+
callable $processOutput
2829
): Process {
2930
$process = new Process(
3031
$command,
@@ -42,7 +43,14 @@ public function startProcess(
4243
$process->inheritEnvironmentVariables(true);
4344
}
4445
// @codeCoverageIgnoreEnd
45-
$process->start($callback);
46+
$process->start(
47+
fn (string $type, string $buffer) => $processOutput(
48+
$index,
49+
$process->getPid(),
50+
$type,
51+
$buffer,
52+
),
53+
);
4654

4755
return $process;
4856
}

src/Process/SymfonyProcessFactory.php

+12-4
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,28 @@
1616
use Symfony\Component\Process\InputStream;
1717
use Symfony\Component\Process\Process;
1818

19+
/**
20+
* @phpstan-type ProcessOutput callable(positive-int|0, int|null, string, string): void
21+
*/
1922
interface SymfonyProcessFactory
2023
{
2124
/**
2225
* Starts a single process reading from the given input stream.
2326
*
24-
* @param list<string> $command
25-
* @param array<string, string>|null $environmentVariables
26-
* @param callable(string, string): void $callback
27+
* @param positive-int|0 $index Index of the process amoung the
28+
* list of running processes.
29+
* @param list<string> $command
30+
* @param array<string, string>|null $environmentVariables
31+
* @param ProcessOutput $processOutput A PHP callback which is run whenever
32+
* there is some output available on
33+
* STDOUT or STDERR.
2734
*/
2835
public function startProcess(
36+
int $index,
2937
InputStream $inputStream,
3038
array $command,
3139
string $workingDirectory,
3240
?array $environmentVariables,
33-
callable $callback
41+
callable $processOutput
3442
): Process;
3543
}

src/Process/SymfonyProcessLauncher.php

+44-16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
use Symfony\Component\Process\Process;
1818
use Webmozart\Assert\Assert;
1919
use Webmozarts\Console\Parallelization\Logger\Logger;
20+
use function count;
21+
use function sprintf;
22+
use const PHP_EOL;
2023

2124
/**
2225
* Launches a number of processes and distributes data among these processes.
@@ -25,6 +28,8 @@
2528
* processes as configured in the constructor. Each process receives a share
2629
* of the data set via its standard input, separated by newlines. The size
2730
* of this share can be configured in the constructor (the segment size).
31+
*
32+
* @phpstan-import-type ProcessOutput from ProcessLauncherFactory
2833
*/
2934
final class SymfonyProcessLauncher implements ProcessLauncher
3035
{
@@ -53,12 +58,12 @@ final class SymfonyProcessLauncher implements ProcessLauncher
5358
private Logger $logger;
5459

5560
/**
56-
* @var callable(string, string): void
61+
* @var ProcessOutput
5762
*/
58-
private $callback;
63+
private $processOutput;
5964

6065
/**
61-
* @var Process[]
66+
* @var array<positive-int|0, Process>
6267
*/
6368
private array $runningProcesses = [];
6469

@@ -70,12 +75,14 @@ final class SymfonyProcessLauncher implements ProcessLauncher
7075
private SymfonyProcessFactory $processFactory;
7176

7277
/**
73-
* @param list<string> $command
74-
* @param array<string, string>|null $extraEnvironmentVariables
75-
* @param positive-int $numberOfProcesses
76-
* @param positive-int $segmentSize
77-
* @param callable(string, string): void $callback
78-
* @param callable(): void $tick
78+
* @param list<string> $command
79+
* @param array<string, string>|null $extraEnvironmentVariables
80+
* @param positive-int $numberOfProcesses
81+
* @param positive-int $segmentSize
82+
* @param ProcessOutput $processOutput A PHP callback which is run whenever
83+
* there is some output available on
84+
* STDOUT or STDERR.
85+
* @param callable(): void $tick
7986
*/
8087
public function __construct(
8188
array $command,
@@ -84,7 +91,7 @@ public function __construct(
8491
int $numberOfProcesses,
8592
int $segmentSize,
8693
Logger $logger,
87-
callable $callback,
94+
callable $processOutput,
8895
callable $tick,
8996
SymfonyProcessFactory $processFactory
9097
) {
@@ -94,7 +101,7 @@ public function __construct(
94101
$this->numberOfProcesses = $numberOfProcesses;
95102
$this->segmentSize = $segmentSize;
96103
$this->logger = $logger;
97-
$this->callback = $callback;
104+
$this->processOutput = $processOutput;
98105
$this->tick = $tick;
99106
$this->processFactory = $processFactory;
100107
}
@@ -155,15 +162,31 @@ public function run(iterable $items): int
155162

156163
private function startProcess(InputStream $inputStream): void
157164
{
165+
$index = count($this->runningProcesses);
166+
158167
$process = $this->processFactory->startProcess(
168+
$index,
159169
$inputStream,
160170
$this->command,
161171
$this->workingDirectory,
162172
$this->environmentVariables,
163-
$this->callback,
173+
$this->processOutput,
174+
);
175+
176+
$pid = $process->getPid();
177+
Assert::notNull(
178+
$pid,
179+
sprintf(
180+
'Expected the process #%d to have a PID. None found.',
181+
$index,
182+
),
164183
);
165184

166-
$this->logger->logChildProcessStarted($process->getCommandLine());
185+
$this->logger->logChildProcessStarted(
186+
$index,
187+
$pid,
188+
$process->getCommandLine(),
189+
);
167190

168191
$this->runningProcesses[] = $process;
169192
}
@@ -188,11 +211,13 @@ private function freeTerminatedProcesses(): int
188211
}
189212

190213
/**
191-
* @return 0|positive-int
214+
* @param positive-int|0 $index
215+
*
216+
* @return positive-int|0
192217
*/
193218
private function freeProcess(int $index, Process $process): int
194219
{
195-
$this->logger->logChildProcessFinished();
220+
$this->logger->logChildProcessFinished($index);
196221

197222
unset($this->runningProcesses[$index]);
198223

@@ -219,7 +244,10 @@ private static function getExitCode(Process $process): int
219244
}
220245
// @codeCoverageIgnoreEnd
221246

222-
Assert::natural($exitCode, 'Expected the process to be finished and return a valid exit code.');
247+
Assert::notNull(
248+
$exitCode,
249+
'Expected the process to have an exit code. Got "null" instead.',
250+
);
223251

224252
return $exitCode;
225253
}

0 commit comments

Comments
 (0)