Do not use up retries on requests which timed out during a prompt

Nothing drives the transfers while tick() is blocked, e.g. by a prompt
for credentials, so requests which are still connecting time out with
curl error 28. Since #12662 these are retried, but each retry counts
like any other, so a few prompts can use up all of them, and parallel
requests to a private host which timed out that way abort with
"Invalid credentials" once the first of them got credentials, as
AuthHelper only reuses those for a request which was not retried yet.

tick() now keeps track of how long it was blocked between two ticks,
and a request which spent most of its time in that gap is restarted
without counting towards maxRetries.

Fixes #12402
This commit is contained in:
dualfroz committed 2026-10-17 03:58:45 +02:00
1 parent 44656d3be2
commit 17d32b7184
2 files changed
+52

No files matched your search

@@ -200,6 +200,38 @@ class CurlDownloaderTest extends TestCase
self::assertSame([], $this->readProperty($downloader, 'jobs'), 'a cancelled request must not be sent again');
}
public function testTimeoutWhileTheLoopWasBlockedDoesNotUseUpARetry(): void
{
// the kernel accepts the connection but nothing ever answers, so the request can only time out
$server = stream_socket_server('tcp://127.0.0.1:0');
self::assertIsResource($server);
$address = stream_socket_get_name($server, false);
self::assertIsString($address);
$downloader = $this->createDownloader();
$failure = null;
// a request which has no retries left, e.g. after timing out during earlier prompts
self::callPrivate($downloader, 'initDownload', static function (): void {
}, static function (\Exception $e) use (&$failure): void {
$failure = $e->getMessage();
}, $address, 'http://'.$address.'/packages.json', ['http' => ['timeout' => 1]], null, ['retries' => 3]);
$downloader->tick();
// a prompt for credentials keeps tick() from being called for longer than the request may take
usleep(1500000);
$downloader->tick();
self::assertNull($failure);
$jobs = $this->readProperty($downloader, 'jobs');
self::assertIsArray($jobs);
self::assertCount(1, $jobs, 'the request must have been sent again');
foreach (array_keys($jobs) as $id) {
$downloader->abortRequest((int) $id);
}
fclose($server);
}
public function testRetryWaitsForTheLatestRetryAfterOfItsOrigin(): void
{
$io = new BufferIO();