diff --git a/src/controllers/AssetIndexesController.php b/src/controllers/AssetIndexesController.php index 114d3621ff1..300c597cb83 100644 --- a/src/controllers/AssetIndexesController.php +++ b/src/controllers/AssetIndexesController.php @@ -151,6 +151,14 @@ public function actionProcessIndexingSession(): Response if (!$indexingSession->actionRequired) { $indexingSession = $assetIndexer->processIndexSession($indexingSession); + if ($indexingSession->forceStop) { + $assetIndexer->stopIndexingSession($indexingSession); + return $this->asFailure(data: [ + 'stop' => $sessionId, + 'message' => Craft::t('app', 'There was an issue indexing assets.'), + ]); + } + // If action is now required, we just processed the last entry // To save a round-trip, just pull the session review data if ($indexingSession->actionRequired) { diff --git a/src/models/AssetIndexingSession.php b/src/models/AssetIndexingSession.php index 6cdf0b73cc3..b270360df97 100644 --- a/src/models/AssetIndexingSession.php +++ b/src/models/AssetIndexingSession.php @@ -86,4 +86,10 @@ class AssetIndexingSession extends Model * @since 4.4.0 */ public bool $processIfRootEmpty = false; + + /** + * @var bool Whether we should stop processing the session because there was a problem. + * @since 4.14.0 + */ + public bool $forceStop = false; } diff --git a/src/services/AssetIndexer.php b/src/services/AssetIndexer.php index 6b5da101bd5..ae228f0847b 100644 --- a/src/services/AssetIndexer.php +++ b/src/services/AssetIndexer.php @@ -307,6 +307,26 @@ public function processIndexSession(AssetIndexingSession $indexingSession): Asse // The most likely scenario is that the last entry is being worked on. if (!$indexEntry && !$indexingSession->processIfRootEmpty) { + // if indexEntry is null here, we should also check if there's anything in the assetindexdata table at all + // (if not, it could have been deleted when clearing caches) + // if that table is empty, we'll get into an infinite loop, calling processIndexSession with the same data all the time + // (and it'll be very hard to discard the session via ui) + if ($indexingSession->processedEntries < $indexingSession->totalEntries) { + $count = (new Query()) + ->from([Table::ASSETINDEXDATA]) + ->where([ + 'sessionId' => $indexingSession->id, + 'completed' => false, + 'inProgress' => false, + ]) + ->count(); + + if ((int)$count === 0) { + Craft::warning('The assetindexdata table is empty; Can’t proceed with indexing.'); + $indexingSession->forceStop = true; + } + } + $mutex->release($lockName); return $indexingSession; }