From ff84b32097796106757a088327ff09fad4e18534 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 20 Jul 2015 17:31:45 +0100 Subject: [PATCH] Add degraded mode to try and bypass ipv6/gzip issues, refs #4142, refs #4121 --- doc/articles/troubleshooting.md | 22 +++++++++ src/Composer/Util/RemoteFilesystem.php | 68 ++++++++++++++++++++------ 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 25279bddb..1d2339b7b 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -168,3 +168,25 @@ To enable the swap you can use for example: /sbin/mkswap /var/swap.1 /sbin/swapon /var/swap.1 ``` + +## Degraded Mode + +Due to some intermittent issues on Travis and other systems, we introduced a +degraded network mode which helps Composer finish successfully but disables +a few optimizations. This is enabled automatically when an issue is first +detected. + +If you have been pointed to this page, you want to check a few things: + +- If you are using ESET antivirus, go in "Advanced Settings" and disable "HTTP-scanner" + under "web access protection" +- If you are using IPv6, try disabling it. If that solves your issues, get in touch + with your ISP or server host, the problem is not at the Packagist level but in the + routing rules between you and Packagist (i.e. the internet at large). The best way to get + these fixed is raise awareness to the network engineers that have the power to fix it. + + To disable IPv6 on Linux, try using this command which appends a + rule prefering IPv4 over IPv6 to your config: + + `sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf"` +- If none of the above helped, please report the error. diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 3acb7e3ac..ce30b83a7 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -37,6 +37,7 @@ class RemoteFilesystem private $retryAuthFailure; private $lastHeaders; private $storeAuth; + private $degradedMode = false; /** * Constructor. @@ -155,6 +156,10 @@ class RemoteFilesystem if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } + if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') { + // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol + $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20); + } $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); if ($this->progress) { @@ -186,6 +191,16 @@ class RemoteFilesystem } restore_error_handler(); if (isset($e) && !$this->retry) { + if (false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(array( + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info' + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + throw $e; } @@ -201,36 +216,45 @@ class RemoteFilesystem $result = false; } + if ($this->progress && !$this->retry) { + $this->io->overwriteError(" Downloading: 100%"); + } + // decode gzip if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') { $decode = false; foreach ($http_response_header as $header) { if (preg_match('{^content-encoding: *gzip *$}i', $header)) { $decode = true; - continue; } elseif (preg_match('{^HTTP/}i', $header)) { $decode = false; } } if ($decode) { - if (PHP_VERSION_ID >= 50400) { - $result = zlib_decode($result); - } else { - // work around issue with gzuncompress & co that do not work with all gzip checksums - $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); - } + try { + if (PHP_VERSION_ID >= 50400) { + $result = zlib_decode($result); + } else { + // work around issue with gzuncompress & co that do not work with all gzip checksums + $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); + } - if (!$result) { - throw new TransportException('Failed to decode zlib stream'); + if (!$result) { + throw new TransportException('Failed to decode zlib stream'); + } + } catch (\Exception $e) { + $this->degradedMode = true; + $this->io->writeError(array( + 'Failed to decode response: '.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info' + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } } - if ($this->progress && !$this->retry) { - $this->io->overwriteError(" Downloading: 100%"); - } - // handle copy command if download was successful if (false !== $result && null !== $fileName) { if ('' === $result) { @@ -269,6 +293,16 @@ class RemoteFilesystem $e->setHeaders($http_response_header); } + if (false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(array( + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info' + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + throw $e; } @@ -404,8 +438,12 @@ class RemoteFilesystem } $options = array_replace_recursive($this->options, $additionalOptions); - $options['http']['protocol_version'] = 1.1; - $headers[] = 'Connection: close'; + if (!$this->degradedMode) { + // degraded mode disables HTTP/1.1 which causes issues with some bad + // proxies/software due to the use of chunked encoding + $options['http']['protocol_version'] = 1.1; + $headers[] = 'Connection: close'; + } if ($this->io->hasAuthentication($originUrl)) { $auth = $this->io->getAuthentication($originUrl);