Refresh the location lease while a per-user restore reads its export
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 long import could outlive the lease, letting a cleanup forget the export being restored and free its blobs mid-read.
This commit is contained in:
@@ -53,7 +53,8 @@ final class UserRestoreService {
|
||||
if (!$repo->isRecordedUserExport($manifestPath)) {
|
||||
throw new \InvalidArgumentException("$manifestPath is not a recorded user export for this location");
|
||||
}
|
||||
$source = new RepositoryImportSource($repo, $manifestPath);
|
||||
$beat = LeaseService::throttled($refresh);
|
||||
$source = new RepositoryImportSource($repo, $manifestPath, $beat);
|
||||
$originalUid = $source->getOriginalUid();
|
||||
|
||||
if ($mode === self::MODE_AS_BACKUP) {
|
||||
@@ -80,7 +81,7 @@ final class UserRestoreService {
|
||||
if ($existing !== null) {
|
||||
// Never import over a live account: export its current state first, so nothing
|
||||
// is lost if the restore turns out to be the wrong choice.
|
||||
$safety = new RepositoryExportDestination($repo, $originalUid, LeaseService::throttled($refresh));
|
||||
$safety = new RepositoryExportDestination($repo, $originalUid, $beat);
|
||||
$service->export($safety, $existing);
|
||||
$safetyManifestPath = $safety->manifestPath();
|
||||
// Same race as UserBackupService::backupUser(): reopen the repository inside the
|
||||
@@ -119,7 +120,7 @@ final class UserRestoreService {
|
||||
|
||||
private function withSharedLock(Target $target, callable $fn): mixed {
|
||||
try {
|
||||
// Refreshed while the safety export writes data (see UserBackupService::withSharedLock()).
|
||||
// Refreshed while the safety export writes and the import reads (see UserBackupService::withSharedLock()).
|
||||
return $this->leases->with(PruneService::lockKey($target), LeaseService::SHARED, 3600, $fn);
|
||||
} catch (LockedException) {
|
||||
throw new \RuntimeException('A cleanup of ' . $target->getName() . ' is in progress; try again later');
|
||||
|
||||
@@ -20,6 +20,8 @@ final class RepositoryImportSource implements IImportSource {
|
||||
public function __construct(
|
||||
private Repository $repo,
|
||||
string $manifestPath,
|
||||
/** Called while blobs are read, to keep the location lease alive. */
|
||||
private ?\Closure $heartbeat = null,
|
||||
) {
|
||||
$this->manifest = json_decode($repo->getObject($manifestPath), true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
@@ -40,6 +42,9 @@ final class RepositoryImportSource implements IImportSource {
|
||||
throw new UserMigrationException('Cannot buffer ' . $path);
|
||||
}
|
||||
foreach ($this->repo->readBlobs($entry['b']) as $data) {
|
||||
if ($this->heartbeat !== null) {
|
||||
($this->heartbeat)();
|
||||
}
|
||||
for ($done = 0, $len = strlen($data); $done < $len; $done += $n) {
|
||||
$n = fwrite($stream, $done === 0 ? $data : substr($data, $done));
|
||||
if ($n === false || $n === 0) {
|
||||
|
||||
Reference in New Issue
Block a user