Read ahead over SFTP: take answers phpseclib has read already instead of waiting up to 10 s on the socket

This commit is contained in:
2026-10-19 05:28:26 +02:00
parent 88b719879d
commit 43a71684f9
3 changed files with 37 additions and 4 deletions
@@ -87,6 +87,25 @@ class SftpRangeFetcherTest extends TestCase {
$this->assertSame(SftpRangeFetcher::DEPTH, $connections[1]->maxInFlight);
}
public function testAnAnswerPhpseclibHasReadAlreadyIsTakenWithoutWaitingOnTheSocket(): void {
// The fake's answers are all "read already": its socket never becomes readable, and
// phpseclib's packet buffer holds data. Waiting on the socket would take a second a step.
$connections = $this->connections(2);
$sockets = [];
foreach ($connections as $c) {
$pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$sockets[] = $pair;
$c->fsock = $pair[0];
$c->packet_buffer = 'more';
}
$ranges = array_slice($this->ranges(), 0, 3);
$start = microtime(true);
$this->assertSame($this->expected($ranges), self::collect(SftpRangeFetcher::fetch(self::opened($connections), '/home/', $ranges, $this->tempDir())));
$this->assertLessThan(1.0, microtime(true) - $start);
}
public function testAServerThatCapsAReadStillGivesEveryByte(): void {
$connections = $this->connections(3, array_fill(0, 3, ['cap' => 100000]));
$ranges = $this->ranges();
@@ -468,6 +468,10 @@ final class FakeSftpConnection {
public array $openHandles = [];
/** @var list<string> paths of every OPEN request */
public array $opened = [];
/** @var resource|null like phpseclib's socket (SftpRangeFetcher waits on it) */
public $fsock = null;
/** like phpseclib's: what came in with the last packet and is not read yet */
public string $packet_buffer = '';
/** Whether the SFTP channel is open: phpseclib 2 opens it on the first SFTP call. */
public bool $initialised = false;
/** Packets sent */