-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var #11791
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ | |
* @internal | ||
* @author Jordi Boggiano <j.boggiano@seld.be> | ||
* @author Nicolas Grekas <p@tchwork.com> | ||
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool} | ||
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} | ||
* @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} | ||
*/ | ||
class CurlDownloader | ||
|
@@ -143,7 +143,7 @@ public function download(callable $resolve, callable $reject, string $origin, st | |
/** | ||
* @param mixed[] $options | ||
* | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool} $attributes | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes | ||
* @param non-empty-string $url | ||
* | ||
* @return int internal job id | ||
|
@@ -155,8 +155,15 @@ private function initDownload(callable $resolve, callable $reject, string $origi | |
'redirects' => 0, | ||
'retries' => 0, | ||
'storeAuth' => false, | ||
'ipResolve' => null, | ||
Seldaek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], $attributes); | ||
|
||
if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') { | ||
$attributes['ipResolve'] = 4; | ||
} elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') { | ||
$attributes['ipResolve'] = 6; | ||
} | ||
|
||
$originalOptions = $options; | ||
|
||
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 | ||
|
@@ -199,6 +206,12 @@ private function initDownload(callable $resolve, callable $reject, string $origi | |
curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports | ||
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); | ||
|
||
if ($attributes['ipResolve'] === 4) { | ||
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curl_setopt($curlHandle, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, 0); When using the IPv4 protocol, is it possible to change the default 200ms wait for IPv6 to 0ms? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if resolution is forced to be ipv4, there won't be any happy eyeballs happening, so this timeout won't ever be used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that would be my expectation as well, so I don't think this is necessary. |
||
} elseif ($attributes['ipResolve'] === 6) { | ||
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); | ||
} | ||
|
||
if (function_exists('curl_share_init')) { | ||
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle); | ||
} | ||
|
@@ -352,8 +365,12 @@ public function tick(): void | |
|| (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer')) | ||
) && $job['attributes']['retries'] < $this->maxRetries | ||
) { | ||
$attributes = ['retries' => $job['attributes']['retries'] + 1]; | ||
if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected | ||
Seldaek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$attributes['ipResolve'] = 4; | ||
} | ||
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); | ||
$this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); | ||
$this->restartJobWithDelay($job, $job['url'], $attributes); | ||
continue; | ||
} | ||
|
||
|
@@ -582,7 +599,7 @@ private function isAuthenticatedRetryNeeded(array $job, Response $response): arr | |
* @param Job $job | ||
* @param non-empty-string $url | ||
* | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>} $attributes | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes | ||
*/ | ||
private function restartJob(array $job, string $url, array $attributes = []): void | ||
{ | ||
|
@@ -600,7 +617,7 @@ private function restartJob(array $job, string $url, array $attributes = []): vo | |
* @param Job $job | ||
* @param non-empty-string $url | ||
* | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>} $attributes | ||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes | ||
*/ | ||
private function restartJobWithDelay(array $job, string $url, array $attributes): void | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't make up your mind whether to use |null or ? and here you use both? ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's optional to pass it but it defaults to null so yeah this looks weird but works well enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
?
after a key does not indicate nullability but the fact that this key is optional in the array.