Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var (#11791)
* Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var, fixes #530 * Address feedback * Add warning in diagnose command when COMPOSER_IPRESOLVE is setpull/11794/head
parent
c069174ac7
commit
3491986ad3
|
@ -1244,6 +1244,11 @@ defaults to 12 and must be between 1 and 50. If your proxy has issues with
|
|||
concurrency maybe you want to lower this. Increasing it should generally not result
|
||||
in performance gains.
|
||||
|
||||
### COMPOSER_IPRESOLVE
|
||||
|
||||
Set to `4` or `6` to force IPv4 or IPv6 DNS resolution. This only works when the
|
||||
curl extension is used for downloads.
|
||||
|
||||
### HTTP_PROXY_REQUEST_FULLURI
|
||||
|
||||
If you use a proxy, but it does not support the request_fulluri flag, then you
|
||||
|
|
|
@ -304,6 +304,11 @@ open stream: Operation timed out
|
|||
We recommend you fix your IPv6 setup. If that is not possible, you can try the
|
||||
following workarounds:
|
||||
|
||||
**Generic Workaround:**
|
||||
|
||||
Set the [`COMPOSER_IPRESOLVE=4`](../03-cli.md#composer-ipresolve) environment variable which will force curl to resolve
|
||||
domains using IPv4. This only works when the curl extension is used for downloads.
|
||||
|
||||
**Workaround Linux:**
|
||||
|
||||
On linux, it seems that running this command helps to make ipv4 traffic have a
|
||||
|
|
|
@ -765,16 +765,6 @@ parameters:
|
|||
count: 1
|
||||
path: ../src/Composer/Command/ShowCommand.php
|
||||
|
||||
-
|
||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the left side\\.$#"
|
||||
count: 1
|
||||
path: ../src/Composer/Command/ShowCommand.php
|
||||
|
||||
-
|
||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the right side\\.$#"
|
||||
count: 1
|
||||
path: ../src/Composer/Command/ShowCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\<int, array\\<string, array\\|string\\>\\>, array\\<int, array\\<string, array\\<int, array\\<string, array\\|string\\>\\>\\|string\\|null\\>\\> given\\.$#"
|
||||
count: 2
|
||||
|
@ -4314,7 +4304,7 @@ parameters:
|
|||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||
|
||||
-
|
||||
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
||||
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
||||
count: 1
|
||||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||
|
||||
|
|
|
@ -553,7 +553,7 @@ EOT
|
|||
|
||||
if ($result) {
|
||||
foreach ($result as $message) {
|
||||
$io->write($message);
|
||||
$io->write(trim($message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -776,6 +776,11 @@ EOT
|
|||
$out($iniMessage, 'comment');
|
||||
}
|
||||
|
||||
if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) {
|
||||
$warnings['ipresolve'] = true;
|
||||
$out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment');
|
||||
}
|
||||
|
||||
return count($warnings) === 0 && count($errors) === 0 ? true : $output;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ use React\Promise\Promise;
|
|||
* @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 @@ class CurlDownloader
|
|||
/**
|
||||
* @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 @@ class CurlDownloader
|
|||
'redirects' => 0,
|
||||
'retries' => 0,
|
||||
'storeAuth' => false,
|
||||
'ipResolve' => null,
|
||||
], $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 @@ class CurlDownloader
|
|||
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);
|
||||
} 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 @@ class CurlDownloader
|
|||
|| (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
|
||||
$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 @@ class CurlDownloader
|
|||
* @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 @@ class CurlDownloader
|
|||
* @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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue