Read ahead: a plan that cannot be made leaves the restore to the usual reads; only a whole buffered SFTP packet counts as an answer waiting

This commit is contained in:
2026-10-19 08:54:13 +02:00
parent c95f552cc6
commit 9b85221e50
4 changed files with 41 additions and 12 deletions
+7 -1
View File
@@ -150,7 +150,7 @@ final class SftpRangeFetcher {
$buffered = $vars['requestBuffer'] ?? null;
$channels = $vars['channel_buffers'] ?? null;
if ((is_array($buffered) && isset($job->queue[0]) && array_key_exists($job->queue[0][0], $buffered))
|| (is_string($vars['packet_buffer'] ?? null) && $vars['packet_buffer'] !== '')
|| (is_string($vars['packet_buffer'] ?? null) && self::wholePacket($vars['packet_buffer']))
|| (is_array($channels) && array_filter($channels) !== [])) {
$ready[] = $c;
continue;
@@ -173,6 +173,12 @@ final class SftpRangeFetcher {
return $n === false || $n === 0 ? array_keys($this->jobs) : array_keys($read);
}
/** Whether $buffer starts with a whole SFTP packet (length, then that many bytes). */
private static function wholePacket(string $buffer): bool {
$length = strlen($buffer) >= 4 ? unpack('N', $buffer) : false;
return is_array($length) && strlen($buffer) >= 4 + $length[1];
}
private function start(int $c, int $i): void {
[$path, $offset, $length] = $this->ranges[$i];
$fh = $this->spoolFile();
+14 -10
View File
@@ -243,18 +243,22 @@ final class Repository {
@rmdir($dir);
}
$plan = new ReadAheadPlan(rtrim($target, '/') . '/.ngb-restore-spool-' . bin2hex(random_bytes(4)));
foreach ($this->streamEntries($snapshotId) as $entry) {
if ($prefix !== '' && !str_starts_with($entry['p'], $prefix)) {
continue;
}
foreach ($entry['b'] as $id) {
try {
[$pack, $offset, $length] = $this->index->get($id);
} catch (RepositoryException) {
continue; // the restore reports it when it gets there
try {
foreach ($this->streamEntries($snapshotId) as $entry) {
if ($prefix !== '' && !str_starts_with($entry['p'], $prefix)) {
continue;
}
foreach ($entry['b'] as $id) {
try {
[$pack, $offset, $length] = $this->index->get($id);
} catch (RepositoryException) {
continue; // the restore reports it when it gets there
}
$plan->add('packs/' . substr($pack, 0, 2) . '/' . $pack, $offset, $length);
}
$plan->add('packs/' . substr($pack, 0, 2) . '/' . $pack, $offset, $length);
}
} catch (\Throwable) {
return null; // reading ahead is only faster: the restore itself reads the snapshot again and reports what is wrong
}
if (count($plan->ranges()) < 2 || (!is_dir($plan->spool) && !@mkdir($plan->spool, 0700, true))) {
return null;