From 8e273ac2590fa9c2be7878babe5baf773c69dc99 Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Sun, 11 Oct 2026 12:30:05 +0200 Subject: [PATCH] Backup step: reporting on a committed snapshot cannot fail the run; check the snapshot row after a unique violation --- lib/Service/BackupService.php | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/Service/BackupService.php b/lib/Service/BackupService.php index b2ef6ea..ec78052 100644 --- a/lib/Service/BackupService.php +++ b/lib/Service/BackupService.php @@ -168,14 +168,20 @@ final class BackupService { } if ($state['files']['phase'] === 'done') { $this->recordSnapshot($run, $state['files']); - $this->alerts->storedBytes($target, $repo->storedBytes()); - $stats = $state['files']['stats']; - if (($stats['unreadable'] ?? 0) > 0 || ($stats['changing'] ?? 0) > 0) { - $warnings = BackupRun::warnings($state['files']); - $this->logger->warning(sprintf('NG Backup run %d to %s: %d unreadable (missing from the snapshot): %s; %d changed while being read: %s', - $run->getId(), $target->getName(), $stats['unreadable'] ?? 0, implode(', ', $warnings['unreadable']), - $stats['changing'] ?? 0, implode(', ', $warnings['changing']))); - $this->alerts->runWarnings($run, $target->getName(), (int)($stats['unreadable'] ?? 0), (int)($stats['changing'] ?? 0), $warnings); + // The snapshot is committed: what follows only reports on it and must not turn + // the run into a failure. + try { + $this->alerts->storedBytes($target, $repo->storedBytes()); + $stats = $state['files']['stats']; + if (($stats['unreadable'] ?? 0) > 0 || ($stats['changing'] ?? 0) > 0) { + $warnings = BackupRun::warnings($state['files']); + $this->logger->warning(sprintf('NG Backup run %d to %s: %d unreadable (missing from the snapshot): %s; %d changed while being read: %s', + $run->getId(), $target->getName(), $stats['unreadable'] ?? 0, implode(', ', $warnings['unreadable']), + $stats['changing'] ?? 0, implode(', ', $warnings['changing']))); + $this->alerts->runWarnings($run, $target->getName(), (int)($stats['unreadable'] ?? 0), (int)($stats['changing'] ?? 0), $warnings); + } + } catch (\Throwable $e) { + $this->logger->warning('NG Backup run ' . $run->getId() . ': reporting on the finished backup failed: ' . $e->getMessage(), ['exception' => $e]); } $run->setStatus(Run::DONE); $run->setPhase('done'); @@ -258,10 +264,15 @@ final class BackupService { try { $this->snapshots->insert($s); } catch (\OCP\DB\Exception $e) { - // Recorded by another process between the check above and this insert. + // Recorded by another process between the check above and this insert? if ($e->getReason() !== \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { throw $e; } + try { + $this->snapshots->findOne($run->getTargetId(), $files['snapshot']); + } catch (DoesNotExistException) { + throw $e; + } } }