Skip to content

Commit fc5cecb

Browse files
authored
Merge pull request #8731 from magento-performance/ACPT-1751
2 parents 84e4b7b + 3e22dd0 commit fc5cecb

File tree

13 files changed

+1061
-43
lines changed

13 files changed

+1061
-43
lines changed

app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public function render(\Magento\Framework\DataObject $row)
3333
$class = 'grid-severity-minor';
3434
$text = __('Processing');
3535
break;
36+
case \Magento\Framework\Indexer\StateInterface::STATUS_SUSPENDED:
37+
$class = 'grid-severity-minor';
38+
$text = __('Suspended');
39+
break;
3640
}
3741
return '<span class="' . $class . '"><span>' . $text . '</span></span>';
3842
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Indexer\Console\Command;
9+
10+
use Magento\Framework\App\ObjectManagerFactory;
11+
use Magento\Framework\Console\Cli;
12+
use Magento\Framework\Exception\AlreadyExistsException;
13+
use Magento\Framework\Indexer\IndexerInterface;
14+
use Magento\Framework\Indexer\StateInterface;
15+
use Magento\Indexer\Model\ResourceModel\Indexer\State;
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Input\InputOption;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
21+
/**
22+
* Command for setting index status for indexers.
23+
*/
24+
class IndexerSetStatusCommand extends AbstractIndexerManageCommand
25+
{
26+
/**#@+
27+
* Names of input arguments or options
28+
*/
29+
private const INPUT_KEY_STATUS = 'status';
30+
/**#@- */
31+
32+
/**
33+
* @var State
34+
*/
35+
private State $stateResourceModel;
36+
37+
/**
38+
* @param State $stateResourceModel
39+
* @param ObjectManagerFactory $objectManagerFactory
40+
*/
41+
public function __construct(
42+
State $stateResourceModel,
43+
ObjectManagerFactory $objectManagerFactory
44+
) {
45+
$this->stateResourceModel = $stateResourceModel;
46+
parent::__construct($objectManagerFactory);
47+
}
48+
49+
/**
50+
* @inheritdoc
51+
*/
52+
protected function configure()
53+
{
54+
$this->setName('indexer:set-status')
55+
->setDescription('Sets the specified indexer status')
56+
->setDefinition($this->getInputList());
57+
58+
parent::configure();
59+
}
60+
61+
/**
62+
* @inheritdoc
63+
*/
64+
protected function execute(InputInterface $input, OutputInterface $output)
65+
{
66+
$errors = $this->validate($input);
67+
if ($errors) {
68+
throw new \InvalidArgumentException(implode("\n", $errors));
69+
}
70+
71+
$newStatus = $input->getArgument(self::INPUT_KEY_STATUS);
72+
$indexers = $this->getIndexers($input);
73+
$returnValue = Cli::RETURN_SUCCESS;
74+
75+
foreach ($indexers as $indexer) {
76+
try {
77+
$this->updateIndexerStatus($indexer, $newStatus, $output);
78+
} catch (\Exception $e) {
79+
$output->writeln($e->getMessage());
80+
$returnValue = Cli::RETURN_FAILURE;
81+
}
82+
}
83+
84+
return $returnValue;
85+
}
86+
87+
/**
88+
* Gets list of arguments for the command.
89+
*
90+
* @return InputOption[]
91+
*/
92+
public function getInputList(): array
93+
{
94+
$modeOptions[] = new InputArgument(
95+
self::INPUT_KEY_STATUS,
96+
InputArgument::REQUIRED,
97+
'Indexer status type [' . StateInterface::STATUS_INVALID
98+
. '|' . StateInterface::STATUS_SUSPENDED . '|' . StateInterface::STATUS_VALID . ']'
99+
);
100+
101+
return array_merge($modeOptions, parent::getInputList());
102+
}
103+
104+
/**
105+
* Checks if all CLI command options are provided.
106+
*
107+
* @param InputInterface $input
108+
* @return string[]
109+
*/
110+
private function validate(InputInterface $input): array
111+
{
112+
$errors = [];
113+
$acceptedValues = [
114+
StateInterface::STATUS_INVALID,
115+
StateInterface::STATUS_SUSPENDED,
116+
StateInterface::STATUS_VALID
117+
];
118+
$inputStatus = $input->getArgument(self::INPUT_KEY_STATUS);
119+
120+
if (!in_array($inputStatus, $acceptedValues, true)) {
121+
$acceptedValuesString = '"' . implode('", "', $acceptedValues) . '"';
122+
$errors[] = sprintf(
123+
'Invalid status "%s". Accepted values are %s.',
124+
$inputStatus,
125+
$acceptedValuesString
126+
);
127+
}
128+
129+
return $errors;
130+
}
131+
132+
/**
133+
* Updates the status of a specified indexer.
134+
*
135+
* @param IndexerInterface $indexer
136+
* @param string $newStatus
137+
* @param OutputInterface $output
138+
* @return void
139+
* @throws AlreadyExistsException
140+
*/
141+
private function updateIndexerStatus(IndexerInterface $indexer, string $newStatus, OutputInterface $output): void
142+
{
143+
$state = $indexer->getState();
144+
$previousStatus = $state->getStatus();
145+
$this->stateResourceModel->save($state->setStatus($newStatus));
146+
$currentStatus = $state->getStatus();
147+
148+
if ($previousStatus !== $currentStatus) {
149+
$output->writeln(
150+
sprintf(
151+
"Index status for Indexer '%s' was changed from '%s' to '%s'.",
152+
$indexer->getTitle(),
153+
$previousStatus,
154+
$currentStatus
155+
)
156+
);
157+
} else {
158+
$output->writeln(sprintf("Index status for Indexer '%s' has not been changed.", $indexer->getTitle()));
159+
}
160+
}
161+
}

app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ private function getStatus(Indexer\IndexerInterface $indexer)
9595
case \Magento\Framework\Indexer\StateInterface::STATUS_WORKING:
9696
$status = 'Processing';
9797
break;
98+
case \Magento\Framework\Indexer\StateInterface::STATUS_SUSPENDED:
99+
$status = 'Suspended';
100+
break;
98101
}
99102
return $status;
100103
}

app/code/Magento/Indexer/Model/Indexer.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\Indexer\Model;
88

9+
use Magento\Framework\DataObject;
910
use Magento\Framework\Indexer\ActionFactory;
1011
use Magento\Framework\Indexer\ActionInterface;
1112
use Magento\Framework\Indexer\ConfigInterface;
@@ -14,13 +15,14 @@
1415
use Magento\Framework\Indexer\StateInterface;
1516
use Magento\Framework\Indexer\StructureFactory;
1617
use Magento\Framework\Indexer\IndexerInterfaceFactory;
18+
use Magento\Framework\Indexer\SuspendableIndexerInterface;
1719

1820
/**
1921
* Indexer model.
2022
*
2123
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2224
*/
23-
class Indexer extends \Magento\Framework\DataObject implements IndexerInterface
25+
class Indexer extends DataObject implements IndexerInterface, SuspendableIndexerInterface
2426
{
2527
/**
2628
* @var string
@@ -332,6 +334,16 @@ public function isInvalid()
332334
return $this->getState()->getStatus() == StateInterface::STATUS_INVALID;
333335
}
334336

337+
/**
338+
* Checks whether indexer is suspended.
339+
*
340+
* @return bool
341+
*/
342+
public function isSuspended(): bool
343+
{
344+
return $this->getState()->getStatus() === StateInterface::STATUS_SUSPENDED;
345+
}
346+
335347
/**
336348
* Check whether indexer is working
337349
*

app/code/Magento/Indexer/Model/Processor.php

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Indexer\Model;
79

810
use Magento\Framework\App\ObjectManager;
911
use Magento\Framework\Indexer\ConfigInterface;
1012
use Magento\Framework\Indexer\IndexerInterface;
1113
use Magento\Framework\Indexer\IndexerInterfaceFactory;
14+
use Magento\Framework\Indexer\IndexerRegistry;
15+
use Magento\Framework\Indexer\StateInterface;
1216
use Magento\Framework\Mview\ProcessorInterface;
1317
use Magento\Indexer\Model\Processor\MakeSharedIndexValid;
1418

@@ -47,25 +51,33 @@ class Processor
4751
*/
4852
protected $makeSharedValid;
4953

54+
/**
55+
* @var IndexerRegistry
56+
*/
57+
private IndexerRegistry $indexerRegistry;
58+
5059
/**
5160
* @param ConfigInterface $config
5261
* @param IndexerInterfaceFactory $indexerFactory
5362
* @param Indexer\CollectionFactory $indexersFactory
5463
* @param ProcessorInterface $mviewProcessor
5564
* @param MakeSharedIndexValid|null $makeSharedValid
65+
* @param IndexerRegistry|null $indexerRegistry
5666
*/
5767
public function __construct(
5868
ConfigInterface $config,
5969
IndexerInterfaceFactory $indexerFactory,
6070
Indexer\CollectionFactory $indexersFactory,
6171
ProcessorInterface $mviewProcessor,
62-
?MakeSharedIndexValid $makeSharedValid = null
72+
?MakeSharedIndexValid $makeSharedValid = null,
73+
?IndexerRegistry $indexerRegistry = null
6374
) {
6475
$this->config = $config;
6576
$this->indexerFactory = $indexerFactory;
6677
$this->indexersFactory = $indexersFactory;
6778
$this->mviewProcessor = $mviewProcessor;
6879
$this->makeSharedValid = $makeSharedValid ?: ObjectManager::getInstance()->get(MakeSharedIndexValid::class);
80+
$this->indexerRegistry = $indexerRegistry ?: ObjectManager::getInstance()->get(IndexerRegistry::class);
6981
}
7082

7183
/**
@@ -81,7 +93,9 @@ public function reindexAllInvalid()
8193
$indexer->load($indexerId);
8294
$indexerConfig = $this->config->getIndexer($indexerId);
8395

84-
if ($indexer->isInvalid()) {
96+
if ($indexer->isInvalid() && !$indexer->isSuspended()
97+
&& !$this->isSharedIndexSuspended($indexerConfig['shared_index'])
98+
) {
8599
// Skip indexers having shared index that was already complete
86100
$sharedIndex = $indexerConfig['shared_index'] ?? null;
87101
if (!in_array($sharedIndex, $this->sharedIndexesComplete)) {
@@ -97,6 +111,36 @@ public function reindexAllInvalid()
97111
}
98112
}
99113

114+
/**
115+
* Checks if any indexers within a group that share a common 'shared_index' ID are suspended.
116+
*
117+
* @param string|null $sharedIndexId
118+
* @return bool
119+
*/
120+
private function isSharedIndexSuspended(?string $sharedIndexId): bool
121+
{
122+
if ($sharedIndexId === null) {
123+
return false;
124+
}
125+
126+
$indexers = $this->config->getIndexers();
127+
128+
foreach ($indexers as $indexerId => $config) {
129+
// Check if the indexer shares the same 'shared_index'
130+
if (isset($config['shared_index']) && $config['shared_index'] === $sharedIndexId) {
131+
$indexer = $this->indexerRegistry->get($indexerId);
132+
133+
// If any indexer that shares the 'shared_index' is suspended, return true
134+
if ($indexer->getStatus() === StateInterface::STATUS_SUSPENDED) {
135+
return true;
136+
}
137+
}
138+
}
139+
140+
// If none of the shared indexers are suspended, return false
141+
return false;
142+
}
143+
100144
/**
101145
* Regenerate indexes for all indexers
102146
*

app/code/Magento/Indexer/Model/ResourceModel/Indexer/State.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ protected function prepareDataForUpdate($object)
3131
$data = parent::prepareDataForUpdate($object);
3232

3333
if (isset($data['status']) && StateInterface::STATUS_VALID === $data['status']) {
34+
$condition = $this->getConnection()->quoteInto(
35+
'status IN (?)',
36+
[
37+
StateInterface::STATUS_WORKING,
38+
StateInterface::STATUS_SUSPENDED,
39+
StateInterface::STATUS_INVALID
40+
]
41+
);
3442
$data['status'] = $this->getConnection()->getCheckSql(
35-
$this->getConnection()->quoteInto('status = ?', StateInterface::STATUS_WORKING),
43+
$condition,
3644
$this->getConnection()->quote($data['status']),
3745
'status'
3846
);

0 commit comments

Comments
 (0)