Skip to content

Commit

Permalink
Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE …
Browse files Browse the repository at this point in the history
…env var, fixes #530
  • Loading branch information
Seldaek committed Jan 10, 2024
1 parent 3427bee commit 8d37beb
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 4 deletions.
5 changes: 5 additions & 0 deletions doc/03-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 curl
is present.

### HTTP_PROXY_REQUEST_FULLURI

If you use a proxy, but it does not support the request_fulluri flag, then you
Expand Down
5 changes: 5 additions & 0 deletions doc/articles/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` environment variable which will force curl to resolve
domains using IPv4.

**Workaround Linux:**

On linux, it seems that running this command helps to make ipv4 traffic have a
Expand Down
19 changes: 15 additions & 4 deletions src/Composer/Util/Http/CurlDownloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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} $attributes
* @param non-empty-string $url
*
* @return int internal job id
Expand All @@ -155,6 +155,7 @@ private function initDownload(callable $resolve, callable $reject, string $origi
'redirects' => 0,
'retries' => 0,
'storeAuth' => false,
'ipResolve' => null,
], $attributes);

$originalOptions = $options;
Expand Down Expand Up @@ -199,6 +200,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 || Platform::getEnv('COMPOSER_IPRESOLVE') === '4') {
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
} elseif ($attributes['ipResolve'] === 6 || Platform::getEnv('COMPOSER_IPRESOLVE') === '6') {
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
}

if (function_exists('curl_share_init')) {
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
}
Expand Down Expand Up @@ -352,8 +359,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
$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;
}

Expand Down Expand Up @@ -582,7 +593,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
{
Expand All @@ -600,7 +611,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
{
Expand Down

0 comments on commit 8d37beb

Please sign in to comment.