setName('core:reset-invalidations'); $this->setDescription('Resets invalidations that are stuck in the "in progress" state, allowing them to be reprocessed.'); $this->addRequiredValueOption( 'processing-host', null, 'Restrict the reset to invalidations assigned to the specified host. Can be used multiple times to target multiple hosts.', null, true ); $this->addRequiredValueOption( 'idsite', null, 'Specify the site ID for which invalidations should be reset. Can be used multiple times to target multiple sites.', null, true ); $this->addRequiredValueOption( 'older-than', null, 'Only reset invalidations that were started before the given time. Accepts any date format parsable by `strtotime` (e.g. "1 day ago", "2024-01-01 12:00:00").' ); $this->addRequiredValueOption( 'newer-than', null, 'Only reset invalidations that were started after the given time. Accepts any date format parsable by `strtotime` (e.g. "1 hour ago", "2024-02-01").' ); $this->addNoValueOption( 'dry-run', null, 'Perform a dry run without making changes. Shows which invalidations would be reset without actually modifying them.' ); $this->setHelp( 'This command allows administrators to reset stuck invalidations that are incorrectly marked as "in progress". ' . 'This can happen if an archiving process was interrupted, such as during a server crash or a deployment, leaving ' . 'invalidations in a stuck state. Resetting them ensures they can be reprocessed in the next archiving run. ⚠ Warning: Only reset invalidations when you are certain they are no longer being processed. ⚠ Resetting active invalidations can lead to incomplete archives, data inconsistencies and wasted processing resources. Usage examples: - Reset all stuck invalidations for site ID 1 that were started more than an hour ago: `./console core:reset-invalidations --idsite=1 --older-than="1 hour ago"` - Reset invalidations assigned to a specific host: `./console core:reset-invalidations --processing-host=archiver1.example.com` - Perform a dry run to check which invalidations would be reset: `./console core:reset-invalidations --idsite=1 --older-than="1 hour ago" --dry-run` - Reset invalidations for multiple sites and hosts: `./console core:reset-invalidations --idsite=1 --idsite=10 --processing-host=archiver1 --processing-host=archiver2` Use this command with caution, especially when resetting invalidations while archiving processes are still in progress.' ); } protected function doExecute(): int { $dryRun = $this->getInput()->getOption('dry-run'); try { $startTime = $this->getStartTime(); } catch (\Exception $e) { throw new \Exception('Invalid value for --newer-than provided.', $e->getCode(), $e); } try { $endTime = $this->getEndTime(); } catch (\Exception $e) { throw new \Exception('Invalid value for --older-than provided.', $e->getCode(), $e); } $model = new Model(); $invalidations = $model->getInvalidationsInProgress( $this->getIdSites(), $this->getProcessingHosts(), $startTime, $endTime ); if (count($invalidations) === 0) { $this->getOutput()->writeln('No invalidations found.'); } elseif ($dryRun) { $this->getOutput()->writeln(count($invalidations) . ' invalidations found:'); if (count($invalidations) > 50) { $invalidations = array_slice($invalidations, 0, 50); $this->getOutput()->writeln('Output limited to oldest 50 records'); } $header = [ 'name', 'idsite', 'report', 'date1', 'date2', 'period', 'ts_invalidated', 'ts_started', 'processing_host', 'process_id', ]; $rows = []; foreach ($invalidations as $invalidation) { $rows[] = [ 'name' => $invalidation['name'], 'idsite' => $invalidation['idsite'], 'report' => $invalidation['report'], 'date1' => $invalidation['date1'], 'date2' => $invalidation['date2'], 'period' => $invalidation['period'], 'ts_invalidated' => $invalidation['ts_invalidated'], 'ts_started' => $invalidation['ts_started'], 'processing_host' => $invalidation['processing_host'], 'process_id' => $invalidation['process_id'], ]; } $this->renderTable($header, $rows); } else { $rowCount = $model->releaseInProgressInvalidations(array_column($invalidations, 'idinvalidation')); $this->getOutput()->writeln('Number of invalidations that were reset: ' . $rowCount); } return self::SUCCESS; } private function getProcessingHosts(): array { $processingHosts = $this->getInput()->getOption('processing-host'); return !is_array($processingHosts) ? [$processingHosts] : $processingHosts; } private function getIdSites(): array { $idSites = $this->getInput()->getOption('idsite'); return Site::getIdSitesFromIdSitesString($idSites); } private function getStartTime(): ?Date { $startTime = $this->getInput()->getOption('newer-than'); if (empty($startTime)) { return null; } return Date::factory($startTime); } private function getEndTime(): ?Date { $endTime = $this->getInput()->getOption('older-than'); if (empty($endTime)) { return null; } return Date::factory($endTime); } }