Commit full-backup snapshots to the catalog under the catalog lock
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / psalm (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled
CI / integration-targets (push) Has been cancelled
CI / integration-user (32) (push) Has been cancelled
CI / integration-user (33) (push) Has been cancelled
CI / integration-user (34) (push) Has been cancelled
CI / integration-user (35) (push) Has been cancelled
CI / integration-full-restore (push) Has been cancelled
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / psalm (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled
CI / integration-targets (push) Has been cancelled
CI / integration-user (32) (push) Has been cancelled
CI / integration-user (33) (push) Has been cancelled
CI / integration-user (34) (push) Has been cancelled
CI / integration-user (35) (push) Has been cancelled
CI / integration-full-restore (push) Has been cancelled
A full backup opened its repository at the start of a step and wrote the catalog at the end of it, without the catalog lock. A per-user export or restore that wrote the catalog in between could lose its entry, or the snapshot could lose its own. The final step now only stores the snapshot object and moves the run to a new 'commit' phase. BackupService then records the snapshot under the catalog lock, from a repository opened inside that lock; if the lock is busy, the next step retries the commit. Repository::writeSnapshot() keeps its old behaviour as putSnapshot() plus recordSnapshot().
This commit is contained in:
@@ -27,13 +27,14 @@ class BackupRunTest extends TestCase {
|
||||
|
||||
$state = BackupRun::start($src);
|
||||
$steps = 0;
|
||||
while ($state['phase'] !== 'done' && $steps < 500) {
|
||||
while ($state['phase'] !== 'commit' && $steps < 500) {
|
||||
// Fresh repository object per step, state through JSON: as separate processes would.
|
||||
$repo = Repository::openWithKey($backend, $keys);
|
||||
$state = json_decode(json_encode(BackupRun::step($repo, $state, microtime(true) + 0.05)), true);
|
||||
$steps++;
|
||||
}
|
||||
$this->assertSame('done', $state['phase']);
|
||||
$this->assertSame('commit', $state['phase']);
|
||||
Repository::openWithKey($backend, $keys)->recordSnapshot($state['snapshot']);
|
||||
$this->assertGreaterThan(2, $steps, 'the run was actually split into steps');
|
||||
|
||||
$target = $this->tempDir();
|
||||
@@ -50,7 +51,7 @@ class BackupRunTest extends TestCase {
|
||||
|
||||
$state = BackupRun::start($src);
|
||||
$mutated = false;
|
||||
for ($i = 0; $i < 500 && $state['phase'] !== 'done'; $i++) {
|
||||
for ($i = 0; $i < 500 && $state['phase'] !== 'commit'; $i++) {
|
||||
$state = BackupRun::step(Repository::openWithKey($backend, $keys), $state, microtime(true) + 0.01);
|
||||
if (!$mutated && $state['cur'] !== null && $state['cur']['offset'] > 0) {
|
||||
$fh = fopen("$src/big.bin", 'r+b');
|
||||
@@ -60,6 +61,8 @@ class BackupRunTest extends TestCase {
|
||||
$mutated = true;
|
||||
}
|
||||
}
|
||||
$this->assertSame('commit', $state['phase']);
|
||||
Repository::openWithKey($backend, $keys)->recordSnapshot($state['snapshot']);
|
||||
$this->assertTrue($mutated);
|
||||
$this->assertGreaterThanOrEqual(1, $state['stats']['retried'] ?? 0);
|
||||
$target = $this->tempDir();
|
||||
@@ -78,14 +81,17 @@ class BackupRunTest extends TestCase {
|
||||
Repository::initWithKey($backend, $keys, $keys->wrap('pw'));
|
||||
$run = function (?string $parent) use ($backend, $src, $keys): array {
|
||||
$state = BackupRun::start(['data' => $src], $parent);
|
||||
for ($i = 0; $i < 2000 && $state['phase'] !== 'done'; $i++) {
|
||||
for ($i = 0; $i < 2000 && $state['phase'] !== 'commit'; $i++) {
|
||||
$state = json_decode(json_encode(BackupRun::step(Repository::openWithKey($backend, $keys), $state, microtime(true))), true);
|
||||
}
|
||||
if ($state['phase'] === 'commit') {
|
||||
Repository::openWithKey($backend, $keys)->recordSnapshot($state['snapshot']);
|
||||
}
|
||||
return $state;
|
||||
};
|
||||
$first = $run(null);
|
||||
$second = $run($first['snapshot']);
|
||||
$this->assertSame('done', $second['phase']);
|
||||
$this->assertSame('commit', $second['phase']);
|
||||
$this->assertGreaterThan(150, $second["steps"], "one file per step");
|
||||
$this->assertSame(0, $second['stats']['read'], 'merge-join with the parent across steps reuses every file');
|
||||
$this->assertSame(200, $second['stats']['reused']);
|
||||
|
||||
Reference in New Issue
Block a user