Skip to content

Commit 94fb923

Browse files
committed
Add --exclude-group to cron:run
https://devdocs.magento.com/guides/v2.4/config-guide/cli/config-cli-subcommands-cron.html#config-cli-cron-group-run When you decide to split a single cron group with the `--group` flag it means you need to do so for every group in your instance. It's pretty common for third party modules to define their own groups whenever they like, so we have to keep up to date with the defined groups in the instance to keep adding these to our crontab when required. One of my instances currently has 11 groups to manage, more will come in the future. In my case I only really want to run the crons like ``` * * * * * bin/magento cron:run --exclude-group index * * * * * flock -n index.lock bin/magento cron:run --group index ``` This will give me better granular control over the indexer crons which can be useful when your store reaches hundreds of thousands of SKUs For future proofing I've made the `--exclude-group` flag stackable so you can define it multiple times like ``` * * * * * bin/magento cron:run --exclude-group index --exclude-group consumers ```
1 parent 84b69d1 commit 94fb923

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

app/code/Magento/Cron/Console/Command/CronCommand.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class CronCommand extends Command
2929
*/
3030
const INPUT_KEY_GROUP = 'group';
3131

32+
/**
33+
* Name of input option
34+
*/
35+
const INPUT_KEY_EXCLUDE_GROUP = 'exclude-group';
36+
3237
/**
3338
* Object manager factory
3439
*
@@ -70,6 +75,12 @@ protected function configure()
7075
InputOption::VALUE_REQUIRED,
7176
'Run jobs only from specified group'
7277
),
78+
new InputOption(
79+
self::INPUT_KEY_EXCLUDE_GROUP,
80+
null,
81+
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
82+
'Exclude jobs from the specified group'
83+
),
7384
new InputOption(
7485
Cli::INPUT_KEY_BOOTSTRAP,
7586
null,
@@ -100,6 +111,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
100111
$objectManager = $this->objectManagerFactory->create($omParams);
101112

102113
$params[self::INPUT_KEY_GROUP] = $input->getOption(self::INPUT_KEY_GROUP);
114+
$params[self::INPUT_KEY_EXCLUDE_GROUP] = $input->getOption(self::INPUT_KEY_EXCLUDE_GROUP);
103115
$params[ProcessCronQueueObserver::STANDALONE_PROCESS_STARTED] = '0';
104116
$bootstrap = $input->getOption(Cli::INPUT_KEY_BOOTSTRAP);
105117
if ($bootstrap) {

app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ function ($a, $b) {
250250
if (!$this->isGroupInFilter($groupId)) {
251251
continue;
252252
}
253+
if ($this->isGroupInExcludeFilter($groupId)) {
254+
continue;
255+
}
253256
if ($this->_request->getParam(self::STANDALONE_PROCESS_STARTED) !== '1'
254257
&& $this->getCronGroupConfigurationValue($groupId, 'use_separate_process') == 1
255258
) {
@@ -795,6 +798,18 @@ private function isGroupInFilter($groupId): bool
795798
&& trim($this->_request->getParam('group'), "'") !== $groupId);
796799
}
797800

801+
/**
802+
* Is Group In Exclude Filter.
803+
*
804+
* @param string $groupId
805+
* @return bool
806+
*/
807+
private function isGroupInExcludeFilter($groupId): bool
808+
{
809+
$excludeGroup = $this->_request->getParam('exclude-group', []);
810+
return in_array($groupId, $excludeGroup);
811+
}
812+
798813
/**
799814
* Process pending jobs.
800815
*

dev/tests/integration/testsuite/Magento/Cron/Observer/ProcessCronQueueObserverTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\Cron\Observer;
77

8+
use Magento\Cron\Observer\ProcessCronQueueObserver;
89
use \Magento\TestFramework\Helper\Bootstrap;
910

1011
class ProcessCronQueueObserverTest extends \PHPUnit\Framework\TestCase
@@ -49,4 +50,102 @@ public function testDispatchNoFailed()
4950
$this->fail($item->getMessages());
5051
}
5152
}
53+
54+
/**
55+
* @param array $expectedGroupsToRun
56+
* @param null $group
57+
* @param null $excludeGroup
58+
* @dataProvider groupFiltersDataProvider
59+
*/
60+
public function testGroupFilters(array $expectedGroupsToRun, $group = null, $excludeGroup = null)
61+
{
62+
$request = Bootstrap::getObjectManager()->get(\Magento\Framework\App\Console\Request::class);
63+
$lockManager = $this->createMock(\Magento\Framework\Lock\LockManagerInterface::class);
64+
65+
// The jobs are locked when they are run, assert on them to see which groups would run
66+
$expectedLockData = [];
67+
foreach ($expectedGroupsToRun as $expectedGroupToRun) {
68+
$expectedLockData[] = [
69+
ProcessCronQueueObserver::LOCK_PREFIX . $expectedGroupToRun,
70+
ProcessCronQueueObserver::LOCK_TIMEOUT
71+
];
72+
}
73+
74+
// No expected lock data, means we should never call it
75+
if (empty($expectedLockData)){
76+
$lockManager->expects($this->never())
77+
->method('lock');
78+
}
79+
80+
$lockManager->expects($this->exactly(count($expectedLockData)))
81+
->method('lock')
82+
->withConsecutive(...$expectedLockData);
83+
84+
$request->setParams(
85+
[
86+
'group' => $group,
87+
'exclude-group' => $excludeGroup,
88+
'standaloneProcessStarted' => '1'
89+
]
90+
);
91+
$this->_model = Bootstrap::getObjectManager()
92+
->create(\Magento\Cron\Observer\ProcessCronQueueObserver::class, [
93+
'request' => $request,
94+
'lockManager' => $lockManager
95+
]);
96+
$this->_model->execute(new \Magento\Framework\Event\Observer());
97+
}
98+
99+
/**
100+
* @return array|array[]
101+
*/
102+
public function groupFiltersDataProvider(): array
103+
{
104+
$listOfGroups = [];
105+
$config = Bootstrap::getObjectManager()->get(\Magento\Cron\Model\ConfigInterface::class);
106+
foreach (array_keys($config->getJobs()) as $groupId) {
107+
$listOfGroups[$groupId] = $groupId;
108+
}
109+
$listOfGroups = array_reverse($listOfGroups, true);
110+
111+
return [
112+
'no flags runs all groups' => [
113+
$listOfGroups // groups to run
114+
],
115+
'--group=default should run' => [
116+
['default'], // groups to run
117+
'default', // --group default
118+
],
119+
'--group=default with --exclude-group=default, nothing should run' => [
120+
[], // groups to run
121+
'default', // --group default
122+
['default'], // --exclude-group default
123+
],
124+
'--group=default with --exclude-group=index, default should run' => [
125+
['default'], // groups to run
126+
'default', // --group default
127+
['index'], // --exclude-group index
128+
],
129+
'--group=index with --exclude-group=default, index should run' => [
130+
['index'], // groups to run
131+
'index', // --group index
132+
['default'], // --exclude-group default
133+
],
134+
'--exclude-group=index, all other groups should run' => [
135+
array_filter($listOfGroups, function($g) { return $g !== 'index'; }), // groups to run, all but index
136+
null, //
137+
['index'] // --exclude-group index
138+
],
139+
'--exclude-group for every group runs nothing' => [
140+
[], // groups to run, none
141+
null, //
142+
$listOfGroups // groups to exclude, all of them
143+
],
144+
'exclude all groups but consumers, consumers runs' => [
145+
array_filter($listOfGroups, function($g) { return $g === 'consumers'; }),
146+
null,
147+
array_filter($listOfGroups, function($g) { return $g !== 'consumers'; })
148+
],
149+
];
150+
}
52151
}

0 commit comments

Comments
 (0)