Skip to content

Commit

Permalink
Implement new sys:cron:kill command (netz98#1226)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmuench committed Jun 20, 2023
1 parent 060d4d3 commit e464efa
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ RECENT CHANGES

- Add: #1177: New commands to manage sales sequences (by Jeroen Boersma)
- Add: #1176: New command to redeploy base packages (by Christian Münch)
- Add: #1226: New sys:cron:kill command (by Christian Münch)
- Imp: #1182: Add debug output if Magento Core Commands cannot be used (by Christian Münch)
- Imp: #1185: Do less compatibility checks (by Christian Münch)
- Imp: Update 3rd party dependencies (php-cs-fixer, psysh, phpstan, phpunit, requests, symfony)
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,17 @@ If no `job` argument is passed you can select a job from a list.
See it in action: http://www.youtube.com/watch?v=QkzkLgrfNaM
If option schedule is present, cron is not launched, but just scheduled immediately in magento crontab.
### Kill a running job
```sh
n98-magerun2.phar sys:cron:kill [--timeout <seconds>] [job_code]
```
If no job is specified a interactive selection of all running jobs is shown.
Jobs can only be killed if the process runs on the same machine as n98-magerun2.
Default timeout of a process kill is 5 seconds.
### Cronjob History
Last executed cronjobs with status.
Expand Down
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ commands:
- N98\Magento\Command\Script\Repository\RunCommand
- N98\Magento\Command\System\CheckCommand
- N98\Magento\Command\System\Cron\HistoryCommand
- N98\Magento\Command\System\Cron\KillCommand
- N98\Magento\Command\System\Cron\ListCommand
- N98\Magento\Command\System\Cron\RunCommand
- N98\Magento\Command\System\Cron\ScheduleCommand
Expand Down
162 changes: 162 additions & 0 deletions src/N98/Magento/Command/System/Cron/KillCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php
/**
* @copyright Copyright (c) netz98 GmbH (https://www.netz98.de)
*
* @see PROJECT_LICENSE.txt
*/

declare(strict_types=1);

namespace N98\Magento\Command\System\Cron;

use _PHPStan_a3459023a\Symfony\Component\Console\Command\Command;
use Magento\Cron\Model\Schedule;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Process\Process;

class KillCommand extends AbstractCronCommand
{
protected function configure()
{
$help = <<<HELP
This command requires a job code as an argument.
It will then kill the process for that job code.
Please note that any job which should be killed has to run
on the same machine as n98-magerun2.
HELP;
$this
->setName('sys:cron:kill')
->addOption(
'timeout',
't',
InputOption::VALUE_OPTIONAL,
'Timeout in seconds',
5
)
->addArgument(
'job_code',
InputArgument::OPTIONAL,
'Job code input'
)
->setDescription('Kill cron jobs by code')
->setHelp($help);

parent::configure();
}

protected function interact(InputInterface $input, OutputInterface $output)
{
$jobCode = $input->getArgument('job_code');

// If no job code is provided, make an interactive selection
if (!$jobCode) {
$cronJobs = $this->getAllRunningJobs();
if (count($cronJobs) > 0) {
$helper = $this->getHelper('question');
$jobCodes = $cronJobs->getColumnValues('job_code');
$question = new ChoiceQuestion('Please select a job code to kill', $jobCodes);
$jobCode = $helper->ask($input, $output, $question);
$input->setArgument('job_code', $jobCode);
}
}

parent::interact($input, $output);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
// Check if the POSIX extension is available
if (!extension_loaded('posix')) {
$output->writeln(
sprintf(
'<error>%s</error>',
'The POSIX extension is not available.'
)
);
return Command::FAILURE;
}

$jobCode = $input->getArgument('job_code');

// Get the hostname of the current machine
$currentHostname = gethostname();
$cronJobs = $this->getRunningJobByCode($jobCode);

// If process-kill option is set, send a kill signal to the process
foreach ($cronJobs as $job) {
$pid = $job->getPid();
if ($pid) {

// Check if the job is running on the same machine
if ($job->getHostname() !== $currentHostname) {
$output->writeln("<comment>The job $jobCode is not running on the current machine.</comment>");
continue;
}

// Create a new Process instance that sends a kill signal to the process
$process = new Process(['kill', '-9', $pid]);
$process->run();

if (!$process->isSuccessful()) {
$output->writeln("<error>Failed to kill process $pid for job $jobCode</error>");
continue;
}

// Check if the process is still running every second for up to 5 seconds
// 5s means 5 tries
$tries = $this->getOption('timeout');
while ($tries-- > 0) {
// Check if the process is still running
$process = new Process(['ps', '-p', $pid]);
$process->run();

if (!$process->isSuccessful()) {
// killed jobs are marked as error
$output->writeln("<info>Killed process $pid for job $jobCode</info>");
$job->setStatus(Schedule::STATUS_ERROR);
$job->save();

return Command::SUCCESS;
}

// Wait for a second before checking again
\sleep(1);
}

$output->writeln("<comment>The process $pid for job $jobCode is still running after sending the kill signal.</comment>");
return Command::FAILURE;
}
}

$output->writeln("<info>No process found to kill</info>");

return Command::SUCCESS;
}

/**
* @param $jobCode
* @return \Magento\Cron\Model\Schedule
*/
protected function getRunningJobByCode($jobCode): \Magento\Cron\Model\Schedule
{
return $this->cronScheduleCollection
->resetData()
->addFieldToFilter('job_code', $jobCode)
->addFieldToFilter('status', \Magento\Cron\Model\Schedule::STATUS_RUNNING)
->getFirstItem();
}

protected function getAllRunningJobs(): \Magento\Cron\Model\ResourceModel\Schedule\Collection
{
return $this->cronScheduleCollection
->addFieldToFilter('status', \Magento\Cron\Model\Schedule::STATUS_RUNNING)
->load();

return $this->cronScheduleCollection;
}
}
2 changes: 2 additions & 0 deletions tests/phar-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ function test_magerun_commands() {
assert_command_contains "search:engine:list" "label"
# sys:check
assert_command_contains "sys:check" "Env"
# sys:cron:kill
assert_command_contains "sys:cron:kill not_exiting_job_code" "No process found to kill"
# sys:cron:list
assert_command_contains "sys:cron:list" "indexer_reindex_all_invalid"
# sys:cron:run
Expand Down

0 comments on commit e464efa

Please sign in to comment.