Skip to content
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

Kill other processes is port is taken #211

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2.2.1 under development

- Enh #211: Add ability to terminate the processes that taken the port (@xepozz)
- Enh #207: Add `--open` option for `serve` command (@xepozz)
- Enh #207: Print possible options for `serve` command (@xepozz)

Expand Down
63 changes: 59 additions & 4 deletions src/Command/Serve.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function configure(): void
$this->defaultWorkers
)
->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'It is only used for testing.')
->addOption('open', 'o', InputOption::VALUE_OPTIONAL, 'Opens the serving server in the default browser.')
->addOption('open', 'o', InputOption::VALUE_OPTIONAL, 'Opens the serving server in the default browser.', false)
->addOption('xdebug', 'x', InputOption::VALUE_OPTIONAL, 'Enables XDEBUG session.', false);
}

Expand Down Expand Up @@ -124,6 +124,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int

if (!str_contains($address, ':')) {
$address .= ':' . $port;
} else {
$port = explode(':', $address)[1];
}

if (!is_dir($documentRoot)) {
Expand All @@ -132,8 +134,54 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

if ($this->isAddressTaken($address)) {
$io->error("http://$address is taken by another process.");
return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
if ($this->isWindows()) {
$io->error("Port {$port} is taken by another process.");
return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
}

/** @psalm-suppress ForbiddenCode */
$runningCommandPIDs = trim((string) shell_exec('lsof -ti :8080 -s TCP:LISTEN'));
if (empty($runningCommandPIDs)) {
$io->error("Port {$port} is taken by another process.");
return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
}

$runningCommandPIDs = array_filter(explode("\n", $runningCommandPIDs));
sort($runningCommandPIDs);

$io->block(
[
"Port {$port} is taken by the processes:",
...array_map(
/** @psalm-suppress ForbiddenCode, PossiblyNullArgument */
fn (string $pid) => sprintf(
'#%s: %s',
$pid,
shell_exec("ps -o command= -p {$pid}"),
),
$runningCommandPIDs,
),
],
'ERROR',
'error',
);
if (!$io->confirm('Kill the process', true)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!$io->confirm('Kill the process', true)) {
if (!$io->confirm('Kill the process' . (count($runningCommandPIDs) > 1 ? 'es' : ''), true)) {

return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
}
$io->info([
'Stopping the processes...',
]);
$out = array_filter(
array_map(
/** @psalm-suppress ForbiddenCode */
fn (string $pid) => shell_exec("kill -9 {$pid}"),
$runningCommandPIDs,
)
);
if (!empty($out)) {
$io->error($out);
return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
}
}

if ($router !== null && !file_exists($router)) {
Expand All @@ -150,7 +198,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$xDebugInstalled = extension_loaded('xdebug');
$xDebugEnabled = $isLinux && $xDebugInstalled && $input->hasOption('xdebug') && $input->getOption('xdebug') === null;
$xDebugEnabled = $isLinux && $xDebugInstalled && $input->hasOption('xdebug') && $input->getOption(
'xdebug'
) === null;
Comment on lines +201 to +203
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$xDebugEnabled = $isLinux && $xDebugInstalled && $input->hasOption('xdebug') && $input->getOption(
'xdebug'
) === null;
$xDebugEnabled = $isLinux
&& $xDebugInstalled
&& $input->hasOption('xdebug')
&& $input->getOption('xdebug') === null;


if ($xDebugEnabled) {
$command[] = 'XDEBUG_MODE=debug XDEBUG_TRIGGER=yes';
Expand Down Expand Up @@ -223,4 +273,9 @@ private function getRootPath(): string

return getcwd();
}

private function isWindows(): bool
{
return stripos(PHP_OS, 'Win') === 0;
}
}
19 changes: 13 additions & 6 deletions tests/ServeCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Yii\Console\Tests;

use RuntimeException;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
use Yiisoft\Yii\Console\Command\Serve;
Expand Down Expand Up @@ -120,14 +121,21 @@ public function testErrorWhenAddressIsTaken(): void
$output = $commandCreate->getDisplay(true);

$this->assertStringContainsString(
'[ERROR] http://127.0.0.1:445 is taken by another process.',
'[ERROR] Port 445 is taken by another process.',
$output
);
} else {
$socket = socket_create_listen(8080);
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

$address = '127.0.0.1';
$port = 8081;
socket_bind($sock, $address, $port) || throw new RuntimeException('Could not bind to address');

socket_listen($sock);

$commandCreate->execute([
'address' => '127.0.0.1:8080',
'address' => '127.0.0.1',
'--port' => $port,
'--docroot' => 'tests',
'--env' => 'test',
]);
Expand All @@ -137,11 +145,10 @@ public function testErrorWhenAddressIsTaken(): void
$this->assertSame(Serve::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS, $commandCreate->getStatusCode());

$this->assertStringContainsString(
'[ERROR] http://127.0.0.1:8080 is taken by another process.',
'[ERROR] Port ' . $port . ' is taken by another process.',
$output
);

socket_close($socket);
socket_close($sock);
}
}

Expand Down
Loading