-
-
Notifications
You must be signed in to change notification settings - Fork 11
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
Do not allow exceptions to propagate from backend #40
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The 'pyproject_api' project provides an implementation of a PEP-517 build frontend. Consider the architecture described in the proposal [1]: +-----------+ +---------------+ +----------------+ | frontend | -spawn-> | child cmdline | -Python-> | backend | | | | interface | | implementation | +-----------+ +---------------+ +----------------+ We can map the these components to 'pyproject_api' components like so: frontend -> pyproject_api._frontend.Frontend (or rather, a concrete implementation of same) child cmdline implementation -> pyproject_api._backend backend implementation -> (the backend defined by the '[build-system] build-backend' key in 'pyproject.yaml', typically as parsed by the 'pyproject_api._frontend.Frontend.create_args_from_folder' method) In both the 'pyproject_api._via_fresh_subprocess.SubprocessFrontend' and 'tox.tox_env.python.virtual_env.package.pyproject.Pep517VirtualEnvFrontend' concrete implementations of 'pyproject_api._frontend.Frontend', the child cmdline implementation provided by 'pyproject_api._backend' is called via subprocess. However, there is a crucial difference: while the former will start a new subprocess for each command invoked, the latter will reuse the backend. This can be problematic. Communication between the frontends and the child cmdline implementation is done by issuing commands in serialized JSON format to the 'pyproject_api._backend' executable via stdin, then waiting for a special "Backend: Wrote response foo to $file" message to appear on stdout, before finally reading the contents of the (JSON-formatted) file at '$file'. In the success case, the file at '$file' will contain a single key, 'result', while in the error case the file will contain three keys, 'code', 'exc_type' and 'exc_msg'. The contents of the latter keys are generated by catching any exception raised by the build backend and processing it. However - and here is the crux of the issue - if these exceptions are not derived from 'Exception' then we allow them to bubble up, killing the backend process in the process. A comment inline suggests this is intended to allow things like 'SystemExit' or 'KeyboardInterrupt', but setuptools' (or rather, distutils') exceptions do not derive from 'Exception' either. This is an issue for frontends that re-use backends like the 'Pep517VirtualEnvFrontend' provided by tox. These backends expect to politely cleanup when the backend fails (as indicated by the presence of 'code', 'exc_type', and 'exc_msg' keys in the result file) by calling a special '_exit' command on the backend. However, if the backend process has died, there's nothing left to answer this request. This causes tox at least to hang forever. There are a two solutions to this. Firstly, we can remove the special case for these two exceptions thus allowing the frontend to manage the lifecycle of the '_backend' executable process regardless. An alternative option would be to add a new key to the result file indicating the backend process has died and the frontend should not expect to be able to do any cleanup, but this would require changing the "interface" defined by this result file and updating all frontend implementations to support this. In this change, we opt for the former since it's simpler and a well behaved frontend should ensure a backend process is never allowed to run forever. [*] Note that this is different to errors the occur while initially starting the backend executable or processing a command. If the backend fails to start, a separate special message is issued ("failed to start backend"), which the frontend is expected to handle, and the exception is re-raised. Invalid requests, meanwhile, are simply ignored and a message is logged which the frontend could choose to handle. [1] https://peps.python.org/pep-0517/#appendix-a-comparison-to-pep-516 Signed-off-by: Stephen Finucane <[email protected]> Closes: tox-dev#39
Can you add a test? |
gaborbernat
requested changes
Dec 26, 2022
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a test for this please, otherwise looks good.
Add some simple-ish tests to prevent regressions. While we're here, we correct a typo in a test filename. Signed-off-by: Stephen Finucane <[email protected]> Related: tox-dev#39
Done 👍 |
You'll need to please the CI gods 😅 and add a changelog entry. |
Signed-off-by: Stephen Finucane <[email protected]>
1.3.0 would be a fine version. |
gaborbernat
approved these changes
Jan 3, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The pyproject_api project provides an implementation of a PEP-517 build frontend. Consider the architecture described in the proposal [1]:
We can map the these components to 'pyproject_api' components like so:
frontend ->
pyproject_api._frontend.Frontend
(or rather, a concrete implementation of same)child cmdline implementation ->
pyproject_api._backend
backend implementation ->
(the backend defined by the
[build-system] build-backend
key inpyproject.yaml
, typically as parsed by thepyproject_api._frontend.Frontend.create_args_from_folder
method)In both the
pyproject_api._via_fresh_subprocess.SubprocessFrontend
andtox.tox_env.python.virtual_env.package.pyproject.Pep517VirtualEnvFrontend
concrete implementations ofpyproject_api._frontend.Frontend
, the child cmdline implementation provided bypyproject_api._backend
is called via subprocess. However, there is a crucial difference: while the former will start a new subprocess for each command invoked, the latter will reuse the backend. This can be problematic. Communication between the frontends and the child cmdline implementation is done by issuing commands in serialized JSON format to thepyproject_api._backend
executable via stdin, then waiting for a specialBackend: Wrote response foo to $file
message to appear on stdout, before finally reading the contents of the (JSON-formatted) file at$file
.In the success case, the file at
$file
will contain a single key,result
, while in the error case the file will contain three keys,code
,exc_type
andexc_msg
. The contents of the latter keys are generated by catching any exception raised by the build backend and processing it. However - and here is the crux of the issue - if these exceptions are not derived fromException
then we allow them to bubble up, killing the backend process in the process. A comment inline suggests this is intended to allow things likeSystemExit
orKeyboardInterrupt
, but setuptools' (or rather, distutils') exceptions do not derive fromException
either.This is an issue for frontends that re-use backends like the
Pep517VirtualEnvFrontend
provided by tox. These backends expect to politely cleanup when the backend fails (as indicated by the presence ofcode
,exc_type
andexc_msg
keys in the result file) by calling a special_exit
command on the backend. However, if the backend process has died, there's nothing left to answer this request. This causes tox at least to hang forever.There are a two solutions to this. Firstly, we can remove the special case for these two exceptions thus allowing the frontend to manage the lifecycle of the
_backend
executable process regardless. Alternatively, we could add a new key to the result file indicating that the backend process has died and the frontend should not expect to be able to do any cleanup. The latter option would require changing the "interface" defined by this result file and updating all frontend implementations to support this. As a result, we opt for the former as the simpler choice. A well behaved frontend should ensure a backend process is never allowed to run forever so the end-user experience should be identical.[*] Note that this is different to errors the occur while initially starting the backend executable or processing a command. If the backend fails to start, a separate special message is issued (
failed to start backend
), which the frontend is expected to handle, and the exception is re-raised. Invalid requests, meanwhile, are simply ignored and a message is logged which the frontend could choose to handle.Closes: #39