diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py index d90ae67cd73..f357b89fef4 100644 --- a/src/pip/_internal/cli/progress_bars.py +++ b/src/pip/_internal/cli/progress_bars.py @@ -14,7 +14,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING if MYPY_CHECK_RUNNING: - from typing import Any, Dict, List + from typing import Any, Dict, List, Optional try: from pip._vendor import colorama @@ -24,6 +24,18 @@ colorama = None +def _signal_unless_backgrounded(signum, handler): + # type: (int, Any) -> Optional[Any] + try: + return signal(signum, handler) + except ValueError: + # FIXME: this otherwise doesn't work when called from a non-main + # thread. This therefore fails if we try to download more than one + # wheel at once via threading, which calls back to Downloader, which + # uses this progress bar. + return None + + def _select_progress_class(preferred, fallback): # type: (Bar, Bar) -> Bar encoding = getattr(preferred.file, "encoding", None) @@ -84,11 +96,8 @@ def __init__(self, *args, **kwargs): **kwargs ) - # FIXME: this otherwise doesn't work when called from a non-main - # thread. This therfore fails if we try to download more than one wheel - # at once. - # self.original_handler = signal(SIGINT, self.handle_sigint) - self.original_handler = None + self.original_handler = _signal_unless_backgrounded( + SIGINT, self.handle_sigint) # If signal() returns None, the previous handler was not installed from # Python, and we cannot restore it. This probably should not happen, @@ -107,7 +116,7 @@ def finish(self): normally, or gets interrupted. """ super(InterruptibleMixin, self).finish() # type: ignore - signal(SIGINT, self.original_handler) + _signal_unless_backgrounded(SIGINT, self.original_handler) def handle_sigint(self, signum, frame): # type: ignore """ diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py index 6b819c0aa4c..4b959001492 100644 --- a/src/pip/_internal/req/req_set.py +++ b/src/pip/_internal/req/req_set.py @@ -7,7 +7,8 @@ from pip._vendor.packaging.utils import canonicalize_name from pip._internal.distributions.shallow_wheel import ( - DistributionNeedingFinalHydration) + DistributionNeedingFinalHydration, +) from pip._internal.exceptions import InstallationError from pip._internal.models.wheel import Wheel from pip._internal.utils import compatibility_tags @@ -66,8 +67,14 @@ def perform_all_final_hydration(self): if not self.dists_needing_final_hydration: return + exceptions = [] def do_hydrate(dist): - dist.finally_hydrate() + # type: (DistributionNeedingFinalHydration) -> None + try: + dist.finally_hydrate() + except Exception as e: + exceptions.append(e) + all_threads = [ threading.Thread( target=do_hydrate, name='download dist {}'.format(dist), @@ -78,6 +85,9 @@ def do_hydrate(dist): t.start() for t in all_threads: t.join() + if exceptions: + raise ValueError('at least one thread failed (errors below):\n{}' + .format('\n'.join(str(e) for e in exceptions))) def add_unnamed_requirement(self, install_req): # type: (InstallRequirement) -> None diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index 623f47f36b1..5c9ad286105 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -7,7 +7,8 @@ from pip._vendor.resolvelib import Resolver as RLResolver from pip._internal.distributions.shallow_wheel import ( - DistributionNeedingFinalHydration) + DistributionNeedingFinalHydration, +) from pip._internal.exceptions import DistributionNotFound, InstallationError from pip._internal.req.req_set import RequirementSet from pip._internal.resolution.base import BaseResolver