Skip to content

Improve the Logger API #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions phpstan-src.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ parameters:

- path: src/CpuCoreCounter.php
message: '#getNumberOfCpuCores\(\) should return#'

# https://github.com/phpstan/phpstan/issues/8222
- path: src/Process/SymfonyProcessLauncher.php
message: '#\$runningProcesses \(array<int<0, max>, .*>\) does not accept non-empty-array<int, .*>\.#'
26 changes: 19 additions & 7 deletions src/Logger/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,32 @@ public function logFinish(string $itemName): void;
public function logItemProcessingFailed(string $item, Throwable $throwable): void;

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

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

/**
* Logs the "unexpected" child output. By unexpected is meant that the main
* process expects the child to output the progress symbol to communicate its
* progression. Any other sort of output is considered "unexpected".
*
* @param string $buffer Child process output.
* @param string $buffer Child process output.
* @param positive-int|0 $index Index of the process amoung the list of running processes.
* @param int|null $pid The child process PID. It can be null if the process is no
* longer running.
*/
public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void;
public function logUnexpectedChildProcessOutput(
int $index,
?int $pid,
string $buffer,
string $progressSymbol
): void;
}
6 changes: 3 additions & 3 deletions src/Logger/NullLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ public function logItemProcessingFailed(string $item, Throwable $throwable): voi
// Do nothing.
}

public function logChildProcessStarted(string $commandName): void
public function logChildProcessStarted(int $index, int $pid, string $commandName): void
{
// Do nothing.
}

public function logChildProcessFinished(): void
public function logChildProcessFinished(int $index): void
{
// Do nothing.
}

public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void
public function logUnexpectedChildProcessOutput(int $index, ?int $pid, string $buffer, string $progressSymbol): void
{
// Do nothing.
}
Expand Down
6 changes: 3 additions & 3 deletions src/Logger/StandardLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,17 @@ public function logItemProcessingFailed(string $item, Throwable $throwable): voi
));
}

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

public function logChildProcessFinished(): void
public function logChildProcessFinished(int $index): void
{
$this->logger->debug('Command finished');
}

public function logUnexpectedChildProcessOutput(string $buffer, string $progressSymbol): void
public function logUnexpectedChildProcessOutput(int $index, ?int $pid, string $buffer, string $progressSymbol): void
{
$this->output->writeln('');
$this->output->writeln(sprintf(
Expand Down
22 changes: 19 additions & 3 deletions src/ParallelExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -353,17 +353,28 @@ private function createProcessLauncher(
$numberOfProcesses,
$segmentSize,
$logger,
fn (string $type, string $buffer) => $this->processChildOutput($buffer, $logger),
fn (int $index, ?int $pid, string $type, string $buffer) => $this->processChildOutput(
$index,
$pid,
$buffer,
$logger,
),
$this->processTick,
);
}

/**
* TODO: pass the type
* Called whenever data is received in the main process from a child process.
*
* @param string $buffer The received data
* @param positive-int|0 $index Index of the process amoung the list of running processes.
* @param int|null $pid The child process PID. It can be null if the process is no
* longer running.
* @param string $buffer The received data
*/
private function processChildOutput(
int $index,
?int $pid,
string $buffer,
Logger $logger
): void {
Expand All @@ -372,7 +383,12 @@ private function processChildOutput(

// Display unexpected output
if ($charactersCount !== mb_strlen($buffer)) {
$logger->logUnexpectedChildProcessOutput($buffer, $progressSymbol);
$logger->logUnexpectedChildProcessOutput(
$index,
$pid,
$buffer,
$progressSymbol,
);
}

$logger->logAdvance($charactersCount);
Expand Down
19 changes: 12 additions & 7 deletions src/Process/ProcessLauncherFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@

use Webmozarts\Console\Parallelization\Logger\Logger;

/**
* @phpstan-type ProcessOutput callable(positive-int|0, int|null, string, string): void
*/
interface ProcessLauncherFactory
{
/**
* @param list<string> $command
* @param array<string, string>|null $extraEnvironmentVariables
* @param positive-int $numberOfProcesses
* @param positive-int $segmentSize
* @param callable(string, string): void $callback
* @param callable(): void $tick
* @param list<string> $command
* @param array<string, string>|null $extraEnvironmentVariables
* @param positive-int $numberOfProcesses
* @param positive-int $segmentSize
* @param ProcessOutput $processOutput A PHP callback which is run whenever
* there is some output available on
* STDOUT or STDERR.
* @param callable(): void $tick
*/
public function create(
array $command,
Expand All @@ -32,7 +37,7 @@ public function create(
int $numberOfProcesses,
int $segmentSize,
Logger $logger,
callable $callback,
callable $processOutput,
callable $tick
): ProcessLauncher;
}
12 changes: 10 additions & 2 deletions src/Process/StandardSymfonyProcessFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
final class StandardSymfonyProcessFactory implements SymfonyProcessFactory
{
public function startProcess(
int $index,
InputStream $inputStream,
array $command,
string $workingDirectory,
?array $environmentVariables,
callable $callback
callable $processOutput
): Process {
$process = new Process(
$command,
Expand All @@ -42,7 +43,14 @@ public function startProcess(
$process->inheritEnvironmentVariables(true);
}
// @codeCoverageIgnoreEnd
$process->start($callback);
$process->start(
fn (string $type, string $buffer) => $processOutput(
$index,
$process->getPid(),
$type,
$buffer,
),
);

return $process;
}
Expand Down
16 changes: 12 additions & 4 deletions src/Process/SymfonyProcessFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,28 @@
use Symfony\Component\Process\InputStream;
use Symfony\Component\Process\Process;

/**
* @phpstan-type ProcessOutput callable(positive-int|0, int|null, string, string): void
*/
interface SymfonyProcessFactory
{
/**
* Starts a single process reading from the given input stream.
*
* @param list<string> $command
* @param array<string, string>|null $environmentVariables
* @param callable(string, string): void $callback
* @param positive-int|0 $index Index of the process amoung the
* list of running processes.
* @param list<string> $command
* @param array<string, string>|null $environmentVariables
* @param ProcessOutput $processOutput A PHP callback which is run whenever
* there is some output available on
* STDOUT or STDERR.
*/
public function startProcess(
int $index,
InputStream $inputStream,
array $command,
string $workingDirectory,
?array $environmentVariables,
callable $callback
callable $processOutput
): Process;
}
60 changes: 44 additions & 16 deletions src/Process/SymfonyProcessLauncher.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
use Symfony\Component\Process\Process;
use Webmozart\Assert\Assert;
use Webmozarts\Console\Parallelization\Logger\Logger;
use function count;
use function sprintf;
use const PHP_EOL;

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

/**
* @var callable(string, string): void
* @var ProcessOutput
*/
private $callback;
private $processOutput;

/**
* @var Process[]
* @var array<positive-int|0, Process>
*/
private array $runningProcesses = [];

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

/**
* @param list<string> $command
* @param array<string, string>|null $extraEnvironmentVariables
* @param positive-int $numberOfProcesses
* @param positive-int $segmentSize
* @param callable(string, string): void $callback
* @param callable(): void $tick
* @param list<string> $command
* @param array<string, string>|null $extraEnvironmentVariables
* @param positive-int $numberOfProcesses
* @param positive-int $segmentSize
* @param ProcessOutput $processOutput A PHP callback which is run whenever
* there is some output available on
* STDOUT or STDERR.
* @param callable(): void $tick
*/
public function __construct(
array $command,
Expand All @@ -84,7 +91,7 @@ public function __construct(
int $numberOfProcesses,
int $segmentSize,
Logger $logger,
callable $callback,
callable $processOutput,
callable $tick,
SymfonyProcessFactory $processFactory
) {
Expand All @@ -94,7 +101,7 @@ public function __construct(
$this->numberOfProcesses = $numberOfProcesses;
$this->segmentSize = $segmentSize;
$this->logger = $logger;
$this->callback = $callback;
$this->processOutput = $processOutput;
$this->tick = $tick;
$this->processFactory = $processFactory;
}
Expand Down Expand Up @@ -155,15 +162,31 @@ public function run(iterable $items): int

private function startProcess(InputStream $inputStream): void
{
$index = count($this->runningProcesses);

$process = $this->processFactory->startProcess(
$index,
$inputStream,
$this->command,
$this->workingDirectory,
$this->environmentVariables,
$this->callback,
$this->processOutput,
);

$pid = $process->getPid();
Assert::notNull(
$pid,
sprintf(
'Expected the process #%d to have a PID. None found.',
$index,
),
);

$this->logger->logChildProcessStarted($process->getCommandLine());
$this->logger->logChildProcessStarted(
$index,
$pid,
$process->getCommandLine(),
);

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

/**
* @return 0|positive-int
* @param positive-int|0 $index
*
* @return positive-int|0
*/
private function freeProcess(int $index, Process $process): int
{
$this->logger->logChildProcessFinished();
$this->logger->logChildProcessFinished($index);

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

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

Assert::natural($exitCode, 'Expected the process to be finished and return a valid exit code.');
Assert::notNull(
$exitCode,
'Expected the process to have an exit code. Got "null" instead.',
);

return $exitCode;
}
Expand Down
Loading