From c26fbb4615e9bc3bbb60b2b96ed4fd24fd7bc31d Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 23 Jun 2024 18:53:44 +0300 Subject: [PATCH 01/15] Kill other processes is port is taken --- src/Command/Serve.php | 58 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index ec40ee89..4eacd62d 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -132,8 +132,42 @@ 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; + $runningCommandPIDs = shell_exec("lsof -ti :8080 -s TCP:LISTEN"); + if (!empty($runningCommandPIDs)) { + $runningCommandPIDs = array_filter(explode("\n", $runningCommandPIDs)); + } + sort($runningCommandPIDs); + + $io->block([ + "Port {$port} is taken by the processes:", + ...array_map( + fn ($pid) => sprintf( + '#%s: %s', + $pid, + shell_exec("ps {$pid} -o command="), + ), + $runningCommandPIDs, + ), + ], + 'ERROR', + 'error', + ); + if (!$io->confirm('Kill the process', true)) { + return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; + } + $io->info([ + 'Stopping the processes...', + ]); + $out = array_filter( + array_map( + fn ($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)) { @@ -189,12 +223,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int $openInBrowser = $input->hasOption('open') && $input->getOption('open') === null; - if ($openInBrowser) { - passthru('open http://' . $address); - } + //if ($openInBrowser) { + // passthru('open http://' . $address); + //} passthru($command, $result); - - return $result; + //$descriptorspec = array( + // 0 => array("pipe", "r"), // stdin - канал, из которого дочерний процесс будет читать + // 1 => array("pipe", "w"), // stdout - канал, в который дочерний процесс будет записывать + // 2 => array("file", "/tmp/error-output.txt", "a") // stderr - файл для записи + //); + + //$cwd = dirname($documentRoot); + //$process = proc_open($command, $descriptorspec, $cwd); + // + //var_dump($process); + + return 0; } /** From 266899e70195dae289ef723be90b3410d92c82b1 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 23 Jun 2024 15:53:59 +0000 Subject: [PATCH 02/15] Apply fixes from StyleCI --- src/Command/Serve.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 4eacd62d..6a205ca3 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -132,23 +132,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($this->isAddressTaken($address)) { - $runningCommandPIDs = shell_exec("lsof -ti :8080 -s TCP:LISTEN"); + $runningCommandPIDs = shell_exec('lsof -ti :8080 -s TCP:LISTEN'); if (!empty($runningCommandPIDs)) { $runningCommandPIDs = array_filter(explode("\n", $runningCommandPIDs)); } sort($runningCommandPIDs); - $io->block([ - "Port {$port} is taken by the processes:", - ...array_map( - fn ($pid) => sprintf( - '#%s: %s', - $pid, - shell_exec("ps {$pid} -o command="), + $io->block( + [ + "Port {$port} is taken by the processes:", + ...array_map( + fn ($pid) => sprintf( + '#%s: %s', + $pid, + shell_exec("ps {$pid} -o command="), + ), + $runningCommandPIDs, ), - $runningCommandPIDs, - ), - ], + ], 'ERROR', 'error', ); From 0077c14d16963967ad67282cbac7be295d46f740 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 23 Jun 2024 18:56:34 +0300 Subject: [PATCH 03/15] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 359a4b93..c677be32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) From 8e07e011f5dc287eed3aa47850a0265ea6b09280 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:09:29 +0300 Subject: [PATCH 04/15] Change arguments ordering --- src/Command/Serve.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 6a205ca3..900012fc 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -145,7 +145,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int fn ($pid) => sprintf( '#%s: %s', $pid, - shell_exec("ps {$pid} -o command="), + shell_exec("ps -o command= {$pid}"), ), $runningCommandPIDs, ), From f3ded17767d1bfe6da979e84e4779d9be956e7a2 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:14:21 +0300 Subject: [PATCH 05/15] Add early returning --- src/Command/Serve.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 900012fc..a0eb4e63 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -132,10 +132,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($this->isAddressTaken($address)) { - $runningCommandPIDs = shell_exec('lsof -ti :8080 -s TCP:LISTEN'); - if (!empty($runningCommandPIDs)) { - $runningCommandPIDs = array_filter(explode("\n", $runningCommandPIDs)); + $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( @@ -185,7 +188,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; if ($xDebugEnabled) { $command[] = 'XDEBUG_MODE=debug XDEBUG_TRIGGER=yes'; From 8789df60078dfbfb6fb149a6d5eeef2bb8eef9be Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 24 Jun 2024 05:14:31 +0000 Subject: [PATCH 06/15] Apply fixes from StyleCI --- src/Command/Serve.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index a0eb4e63..586d4c59 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -189,8 +189,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $xDebugInstalled = extension_loaded('xdebug'); $xDebugEnabled = $isLinux && $xDebugInstalled && $input->hasOption('xdebug') && $input->getOption( - 'xdebug' - ) === null; + 'xdebug' + ) === null; if ($xDebugEnabled) { $command[] = 'XDEBUG_MODE=debug XDEBUG_TRIGGER=yes'; From 997e0466b6179ea319e81f7edc514b2c94b0f01e Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:19:16 +0300 Subject: [PATCH 07/15] Add early returning for windows --- src/Command/Serve.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 586d4c59..641bd563 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -132,6 +132,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($this->isAddressTaken($address)) { + if ($this->isWindows()) { + $io->error("Port {$port} is taken by another process"); + return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; + } + $runningCommandPIDs = trim((string) shell_exec('lsof -ti :8080 -s TCP:LISTEN')); if (empty($runningCommandPIDs)) { $io->error("Port {$port} is taken by another process"); @@ -273,4 +278,9 @@ private function getRootPath(): string return getcwd(); } + + private function isWindows(): bool + { + return stripos(PHP_OS, 'Win') === 0; + } } From b2f45d12d79ce7bc609803d1a2e275610a4f9991 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:20:37 +0300 Subject: [PATCH 08/15] Fix linux command --- src/Command/Serve.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 641bd563..33567e76 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -153,7 +153,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int fn ($pid) => sprintf( '#%s: %s', $pid, - shell_exec("ps -o command= {$pid}"), + shell_exec("ps -o command= -p {$pid}"), ), $runningCommandPIDs, ), From 5a1963488190b83f662bd8673e297d790cb00eb1 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:37:59 +0300 Subject: [PATCH 09/15] Fix tests --- src/Command/Serve.php | 26 ++++++++------------------ tests/ServeCommandTest.php | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 33567e76..af57566f 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -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); } @@ -133,13 +133,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->isAddressTaken($address)) { if ($this->isWindows()) { - $io->error("Port {$port} is taken by another process"); + $io->error("Port {$port} is taken by another process."); return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; } $runningCommandPIDs = trim((string) shell_exec('lsof -ti :8080 -s TCP:LISTEN')); if (empty($runningCommandPIDs)) { - $io->error("Port {$port} is taken by another process"); + $io->error("Port {$port} is taken by another process."); return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; } @@ -234,22 +234,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $openInBrowser = $input->hasOption('open') && $input->getOption('open') === null; - //if ($openInBrowser) { - // passthru('open http://' . $address); - //} + if ($openInBrowser) { + passthru('open http://' . $address); + } passthru($command, $result); - //$descriptorspec = array( - // 0 => array("pipe", "r"), // stdin - канал, из которого дочерний процесс будет читать - // 1 => array("pipe", "w"), // stdout - канал, в который дочерний процесс будет записывать - // 2 => array("file", "/tmp/error-output.txt", "a") // stderr - файл для записи - //); - - //$cwd = dirname($documentRoot); - //$process = proc_open($command, $descriptorspec, $cwd); - // - //var_dump($process); - - return 0; + + return $result; } /** diff --git a/tests/ServeCommandTest.php b/tests/ServeCommandTest.php index e71d42bf..f8b5b52c 100644 --- a/tests/ServeCommandTest.php +++ b/tests/ServeCommandTest.php @@ -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; @@ -124,10 +125,17 @@ public function testErrorWhenAddressIsTaken(): void $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) or 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', ]); @@ -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); } } From 96db915f2c3592e00730f6e312cd825ba5421144 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 24 Jun 2024 05:38:12 +0000 Subject: [PATCH 10/15] Apply fixes from StyleCI --- tests/ServeCommandTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ServeCommandTest.php b/tests/ServeCommandTest.php index f8b5b52c..f804d38a 100644 --- a/tests/ServeCommandTest.php +++ b/tests/ServeCommandTest.php @@ -129,7 +129,7 @@ public function testErrorWhenAddressIsTaken(): void $address = '127.0.0.1'; $port = 8081; - socket_bind($sock, $address, $port) or throw new RuntimeException('Could not bind to address'); + socket_bind($sock, $address, $port) || throw new RuntimeException('Could not bind to address'); socket_listen($sock); From 0bd10701e3d9452427e6e41e9bc620073373a54a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:43:51 +0300 Subject: [PATCH 11/15] Suppress psalm errors --- src/Command/Serve.php | 3 +++ tests/ServeCommandTest.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index af57566f..5a84e6f6 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -137,6 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 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."); @@ -153,6 +154,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int fn ($pid) => sprintf( '#%s: %s', $pid, + /** @psalm-suppress ForbiddenCode */ shell_exec("ps -o command= -p {$pid}"), ), $runningCommandPIDs, @@ -169,6 +171,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ]); $out = array_filter( array_map( + /** @psalm-suppress ForbiddenCode */ fn ($pid) => shell_exec("kill -9 {$pid}"), $runningCommandPIDs, ) diff --git a/tests/ServeCommandTest.php b/tests/ServeCommandTest.php index f804d38a..3ab6f373 100644 --- a/tests/ServeCommandTest.php +++ b/tests/ServeCommandTest.php @@ -121,7 +121,7 @@ public function testErrorWhenAddressIsTaken(): void $output = $commandCreate->getDisplay(true); $this->assertStringContainsString( - '[ERROR] http://127.0.0.1:445 is taken by another process.', + '[ERROR] Port 8080 is taken by another process.', $output ); } else { From 4a6a74ef4419963021e18f91522bf9f5e521c78b Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:44:06 +0300 Subject: [PATCH 12/15] Unpack port from address --- src/Command/Serve.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 5a84e6f6..8b20fed7 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -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)) { From 7ce45b03f918034062b1ae7fb4d5bdc159e018c8 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:44:27 +0300 Subject: [PATCH 13/15] Fix port --- tests/ServeCommandTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ServeCommandTest.php b/tests/ServeCommandTest.php index 3ab6f373..4ee05898 100644 --- a/tests/ServeCommandTest.php +++ b/tests/ServeCommandTest.php @@ -121,7 +121,7 @@ public function testErrorWhenAddressIsTaken(): void $output = $commandCreate->getDisplay(true); $this->assertStringContainsString( - '[ERROR] Port 8080 is taken by another process.', + '[ERROR] Port 445 is taken by another process.', $output ); } else { From c62bd3003c189910635a7f4ac9ea94d691fa1438 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 24 Jun 2024 08:51:35 +0300 Subject: [PATCH 14/15] Fix psalm --- src/Command/Serve.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index 8b20fed7..abc56b19 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -153,10 +153,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int [ "Port {$port} is taken by the processes:", ...array_map( - fn ($pid) => sprintf( + /** @psalm-suppress ForbiddenCode, PossiblyNullArgument */ + fn (string $pid) => sprintf( '#%s: %s', $pid, - /** @psalm-suppress ForbiddenCode */ shell_exec("ps -o command= -p {$pid}"), ), $runningCommandPIDs, @@ -174,7 +174,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $out = array_filter( array_map( /** @psalm-suppress ForbiddenCode */ - fn ($pid) => shell_exec("kill -9 {$pid}"), + fn (string $pid) => shell_exec("kill -9 {$pid}"), $runningCommandPIDs, ) ); From d00acad977d2f70bc4612b72295249464e115743 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 24 Jun 2024 05:51:45 +0000 Subject: [PATCH 15/15] Apply fixes from StyleCI --- src/Command/Serve.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/Serve.php b/src/Command/Serve.php index abc56b19..5cf4248b 100644 --- a/src/Command/Serve.php +++ b/src/Command/Serve.php @@ -153,7 +153,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int [ "Port {$port} is taken by the processes:", ...array_map( - /** @psalm-suppress ForbiddenCode, PossiblyNullArgument */ + /** @psalm-suppress ForbiddenCode, PossiblyNullArgument */ fn (string $pid) => sprintf( '#%s: %s', $pid,