Skip to content
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

Select PEX runtime interpreter robustly. #1770

Merged
merged 3 commits into from
May 18, 2022
Merged

Conversation

jsirois
Copy link
Member

@jsirois jsirois commented May 18, 2022

Previously two proxies for interpreter applicability were used:

  1. The shebang selected interpreter or the explicit interpreter used
    to invoke the PEX.
  2. Any embedded interpreter constraints.

This could lead to selecting an interpreter that was not actually able
to resolve all required distributions from within the PEX, which is the
only real criteria, and a failure to boot.

Fix the runtime interpreter resolution process to test an interpreter
can resolve the PEX before using it or re-execing to it.

In the use case, the resolve test performed is cached work and leads to
no extra overhead. In the re-exec case the resolve test can cost
O(100ms), but at the benefit of ensuring either the selected interpreter
will definitely work or no interpreters on the search path can work.

Fixes #1020

Previously two proxies for interpreter applicability were used:
1. The shebang selected interpreter or the explicit interpreter used
   to invoke the PEX.
2. Any embedded interpreter constraints.

This could lead to selecting an interpreter that was not actually able
to resolve all required distributions from within the PEX, which is the
only real criteria, and a failure to boot.

Fix the runtime interpreter resolution process to test an interpreter
can resolve the PEX before using it or re-execing to it.

In the use case, the resolve test performed is cached work and leads to
no extra overhead. In the re-exec case the resolve test can cost
O(100ms), but at the benefit of ensuring either the selected interpreter
will definitely work or no interpreters on the search path can work.

Fixes pex-tool#1020
target=LocalInterpreter.create(interpreter),
)
try:
pex_environment.resolve()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: This resolve test right here was what indirectly caused the need for #1768 and killing pkg_resources use. That used a bunch of pkg_resources.Requirement.parse and pkg_resources.Distribution finding and loading code and led to the boot-time vendor meta_path importer clash with Pex said same.

target = target or targets.current()
return cls(pex=pex, pex_info=pex_info, target=target)
key = (pex_file, pex_hash, target)
mounted = cls._CACHE.get(key)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new mount caching combines with the pre-existing instance-level resolve() caching to avoid resolving twice in the happy path of the interpreter that tries to run a PEX 1st being a compatible one.

@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

This change probably means shebang could be defaulted to #!/usr/bin/env python, but as discussed ad-nauseum that's still broken sometimes (for many Debian distributions that have python3 but not python). The --sh-boot is probably the safest generic default, but as with that, changing how the shebang default works today (#!/usr/bin/env pythonX.Y) will break some users; so any change will have to wait for Pex 3. Pants though can do as it wishes with this new robustness!

@Eric-Arellano
Copy link
Contributor

In the use case, the resolve test performed is cached work and leads to
no extra overhead.

Which use case? I'm not following.

@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

Yooze, not yoos. Languages are dumb.

In the case where the tested interpreter is the current interpreter and no re-exec is needed, there is no introduced overhead. The resolve performed by the test is re-used later on during the activation phase of the boot.

In the case where the current interpreter is not used and we instead re-exec using the foreign tested interpreter, the test will be run a second time.

Copy link
Contributor

@Eric-Arellano Eric-Arellano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! I remember you talking about wanting to do this at lunch in Tempe 3 years ago but how much refactoring would be necessary.

--

What's the error message if you have only one interpreter in the IC range, but no valid dists for that interpreter?

@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

What's the error message if you have only one interpreter in the IC range, but no valid dists for that interpreter?

The same as it's been for a while when no interpreter meets the criteria:

$ python -mpex --interpreter-constraint ">=2.7,<3.12,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" --python-path /usr/bin/python3.6 psutil -opsutil.pex
$ pex-tools psutil.pex info -i4
{
    "bootstrap_hash": "e5f293d656ebdb2afea96aa18cc4b61a9c1ea06b",
    "build_properties": {
        "pex_version": "2.1.88"
    },
    "code_hash": "3c1b1740a264d619b04e73e72a1a8ec30553f52e",
    "distributions": {
        "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl": "dde6aff9291fce65d926e716a0ac981ce602ef38398c874c7786bcde6f2d7378"
    },
    "emit_warnings": true,
    "ignore_errors": false,
    "includes_tools": false,
    "inherit_path": "false",
    "interpreter_constraints": [
        ">=2.7,<3.12,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
    ],
    "pex_hash": "4c72da3c941e5768385749b7d7d2276eef3eecd7",
    "pex_path": null,
    "requirements": [
        "psutil"
    ],
    "strip_pex_env": true,
    "venv": false,
    "venv_bin_path": "false",
    "venv_copies": false,
    "venv_site_packages_copies": false,
    "pex_root": "/home/jsirois/.pex"
}
$ PEX_PYTHON_PATH=$(which -a python{,2{,.7},3{,.{5,6,7,8,9,10,11}}} | grep -v python3.6 | xargs | tr ' ' ':') python2.7 psutil.pex
Failed to find compatible interpreter on path /home/jsirois/.pyenv/shims/python:/usr/bin/python:/home/jsirois/.pyenv/shims/python2:/usr/bin/python2:/home/jsirois/.pyenv/shims/python2.7:/usr/bin/python2.7:/home/jsirois/.pyenv/shims/python3:/usr/bin/python3:/home/jsirois/.pyenv/shims/python3.5:/home/jsirois/.pyenv/shims/python3.7:/usr/bin/python3.7:/home/jsirois/.pyenv/shims/python3.8:/usr/bin/python3.8:/home/jsirois/.pyenv/shims/python3.9:/home/jsirois/.pyenv/shims/python3.10:/usr/bin/python3.10:/home/jsirois/.pyenv/shims/python3.11:/usr/bin/python3.11.

Examined the following interpreters:
1.)                                    /usr/bin/python2.7 CPython==2.7.18
2.) /home/jsirois/.pyenv/versions/3.11.0b1/bin/python3.11 CPython==3.11.0
3.)                                   /usr/bin/python3.10 CPython==3.10.4
4.)    /home/jsirois/.pyenv/versions/3.5.10/bin/python3.5 CPython==3.5.10
5.)    /home/jsirois/.pyenv/versions/3.7.12/bin/python3.7 CPython==3.7.12
6.)                                    /usr/bin/python3.7 CPython==3.7.13
7.)    /home/jsirois/.pyenv/versions/3.8.12/bin/python3.8 CPython==3.8.12
8.)                                    /usr/bin/python3.8 CPython==3.8.13
9.)    /home/jsirois/.pyenv/versions/3.9.10/bin/python3.9 CPython==3.9.10
10.)   /home/jsirois/.pyenv/versions/3.10.2/bin/python3.10 CPython==3.10.2

No interpreter compatible with the requested constraints was found:
  Version matches >=2.7,<3.12,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*

That's as opposed to before this change where the interpreter nominally met the critera, but failed:

$ pex --interpreter-constraint ">=2.7,<3.12,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" --python-path /usr/bin/python3.6 psutil -opsutil.old.pex
$ PEX_PYTHON_PATH=$(which -a python{,2{,.7},3{,.{5,6,7,8,9,10,11}}} | grep -v python3.6 | xargs | tr ' ' ':') python2.7 psutil.old.pex
Traceback (most recent call last):
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/pex.py", line 496, in execute
    self.activate()
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/pex.py", line 160, in activate
    self._activated_dists = self._activate()
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/pex.py", line 147, in _activate
    activated_dists.extend(env.activate())
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/environment.py", line 312, in activate
    self._activated_dists = self._activate()
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/environment.py", line 661, in _activate
    resolved = self.resolve()
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/environment.py", line 492, in resolve
    for fingerprinted_distribution in self.resolve_dists(all_reqs)
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/environment.py", line 516, in resolve_dists
    for qualified_req_or_not_found in self._root_requirements_iter(reqs):
  File "/home/jsirois/.pex/unzipped_pexes/28f376c8e09545c0c07855d3ce4cec78418b7892/.bootstrap/pex/environment.py", line 457, in _root_requirements_iter
    raise ResolveError(message)
pex.environment.ResolveError: A distribution for psutil could not be resolved in this environment.Found 1 distribution for psutil that do not apply:
1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /usr/bin/python2.7:
cp27-cp27mu-manylinux_2_35_x86_64
... ~600 entries ...
py20-none-manylinux1_x86_64
py20-none-linux_x86_64
py27-none-any
py2-none-any
py26-none-any
py25-none-any
py24-none-any
py23-none-any
py22-none-any
py21-none-any
py20-none-any

Let me see how bad it it to get:

No interpreter compatible with the requested constraints was found:
  Version matches >=2.7,<3.12,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*

Be something like:

No interpreter compatible with the requested constraints was found:
  Is compatible with all PEX requirements:
    ... requirement list ...

The full detail of the:

pex.environment.ResolveError: A distribution for psutil could not be resolved in this environment.Found 1 distribution for psutil that do not apply:
...

Error above is available for each and every interpreter that fails the resolve test, but that's > 6000 lines of error output to have to read through in this example.

@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

I'll follow-up here with the error message change since it's semi-significant. It won't go in the release notes but will go in the release.

@jsirois jsirois merged commit d9e6caf into pex-tool:main May 18, 2022
@jsirois jsirois deleted the issues/1020 branch May 18, 2022 14:12
@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

Hrm, not awesome:

Failed to find compatible interpreter on path /home/jsirois/.pyenv/shims/python:/usr/bin/python:/home/jsirois/.pyenv/shims/python2:/usr/bin/python2:/home/jsirois/.pyenv/shims/python2.7:/usr/bin/python2.7:/home/jsirois/.pyenv/shims/python3:/usr/bin/python3:/home/jsirois/.pyenv/shims/python3.5:/home/jsirois/.pyenv/shims/python3.7:/usr/bin/python3.7:/home/jsirois/.pyenv/shims/python3.8:/usr/bin/python3.8:/home/jsirois/.pyenv/shims/python3.9:/home/jsirois/.pyenv/shims/python3.10:/usr/bin/python3.10:/home/jsirois/.pyenv/shims/python3.11:/usr/bin/python3.11.

Examined the following interpreters:
1.)                                    /usr/bin/python2.7 CPython==2.7.18
2.) /home/jsirois/.pyenv/versions/3.11.0b1/bin/python3.11 CPython==3.11.0
3.)                                   /usr/bin/python3.10 CPython==3.10.4
4.)    /home/jsirois/.pyenv/versions/3.5.10/bin/python3.5 CPython==3.5.10
5.)    /home/jsirois/.pyenv/versions/3.7.12/bin/python3.7 CPython==3.7.12
6.)                                    /usr/bin/python3.7 CPython==3.7.13
7.)    /home/jsirois/.pyenv/versions/3.8.12/bin/python3.8 CPython==3.8.12
8.)                                    /usr/bin/python3.8 CPython==3.8.13
9.)    /home/jsirois/.pyenv/versions/3.9.10/bin/python3.9 CPython==3.9.10
10.)   /home/jsirois/.pyenv/versions/3.10.2/bin/python3.10 CPython==3.10.2

No interpreter compatible with the requested constraints was found:

  A distribution for psutil could not be resolved for /usr/bin/python2.7.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /usr/bin/python2.7:
  cp27-cp27mu-manylinux_2_35_x86_64
  ... 393 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.11.0b1/bin/python3.11.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.11.0b1/bin/python3.11:
  cp311-cp311-manylinux_2_35_x86_64
  ... 887 more ...

  A distribution for psutil could not be resolved for /usr/bin/python3.10.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /usr/bin/python3.10:
  cp310-cp310-manylinux_2_35_x86_64
  ... 816 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.5.10/bin/python3.5.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.5.10/bin/python3.5:
  cp35-cp35m-manylinux_2_35_x86_64
  ... 461 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.7.12/bin/python3.7.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.7.12/bin/python3.7:
  cp37-cp37m-manylinux_2_35_x86_64
  ... 603 more ...

  A distribution for psutil could not be resolved for /usr/bin/python3.7.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /usr/bin/python3.7:
  cp37-cp37m-manylinux_2_35_x86_64
  ... 603 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.8.12/bin/python3.8.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.8.12/bin/python3.8:
  cp38-cp38-manylinux_2_35_x86_64
  ... 674 more ...

  A distribution for psutil could not be resolved for /usr/bin/python3.8.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /usr/bin/python3.8:
  cp38-cp38-manylinux_2_35_x86_64
  ... 674 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.9.10/bin/python3.9.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.9.10/bin/python3.9:
  cp39-cp39-manylinux_2_35_x86_64
  ... 745 more ...

  A distribution for psutil could not be resolved for /home/jsirois/.pyenv/versions/3.10.2/bin/python3.10.
  Found 1 distribution for psutil that do not apply:
  1.) The wheel tags for psutil 5.9.0 are cp36-cp36m-manylinux_2_12_x86_64, cp36-cp36m-manylinux2014_x86_64, cp36-cp36m-manylinux_2_17_x86_64, cp36-cp36m-manylinux2010_x86_64 which do not match the supported tags of /home/jsirois/.pyenv/versions/3.10.2/bin/python3.10:
  cp310-cp310-manylinux_2_35_x86_64
  ... 816 more ...

@jsirois
Copy link
Member Author

jsirois commented May 18, 2022

The error message fix above is in: #1772

Copy link
Collaborator

@benjyw benjyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Late to the party, but looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decouple PEX runtime interpreter selection from buildtime interpreter selection.
3 participants