Skip to content

bpo-37380: subprocess: skip _cleanup and __del__ on win - #14360

Merged
vstinner merged 1 commit into
python:masterfrom
efiop:fix-bpo-37380
Jun 28, 2019
Merged

bpo-37380: subprocess: skip _cleanup and __del__ on win#14360
vstinner merged 1 commit into
python:masterfrom
efiop:fix-bpo-37380

Conversation

@efiop

@efiop efiop commented Jun 25, 2019

Copy link
Copy Markdown
Contributor

As noted by @eryksun in [1] and [2], using _cleanup and _active(in
del) is not necessary on Windows, since:

Unlike Unix, a process in Windows doesn't have to be waited on by
its parent to avoid a zombie. Keeping the handle open will actually
create a zombie until the next _cleanup() call, which may be never
if Popen() isn't called again.

This patch simply defines subprocess._active as None, for which we already
have the proper logic in place in subprocess.Popen.__del__, that prevents it
from trying to append the process to the _active. This patch also defines
subprocess._cleanup as a noop for Windows.

[1] https://bugs.python.org/issue37380#msg346333
[2] https://bugs.python.org/issue36067#msg336262

Signed-off-by: Ruslan Kuprieiev ruslan@iterative.ai

https://bugs.python.org/issue37380

@efiop
efiop requested a review from gpshead as a code owner June 25, 2019 01:09
@the-knights-who-say-ni

Copy link
Copy Markdown

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Our records indicate we have not received your CLA. For legal reasons we need you to sign this before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

If you have recently signed the CLA, please wait at least one business day
before our records are updated.

You can check yourself to see if the CLA has been received.

Thanks again for your contribution, we look forward to reviewing it!

Comment thread Misc/NEWS.d/next/Windows/2019-06-25-04-15-22.bpo-37380.tPxjuz.rst Outdated
Comment thread Lib/subprocess.py Outdated
@efiop
efiop force-pushed the fix-bpo-37380 branch 2 times, most recently from 636dfcf to f59cad6 Compare June 25, 2019 02:18
@efiop

efiop commented Jun 25, 2019

Copy link
Copy Markdown
Contributor Author

Thanks for the review @eryksun ! Addressed your comments in the patch.

Comment thread Lib/subprocess.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's wrong: you must also wait for process completion on Windows.

@efiop efiop Jun 25, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @vstinner ! Thanks for the review! On Windows we just need to close the handle when we no longer need it, so that the kernel can free it. CloseHandle is called as a __del__ in class Handle defined above, so there are no additional actions required here, and so we can just skip the whole __del__ logic here. From [1]

When you no longer need these handles, close them by using the CloseHandle function.

[1] https://docs.microsoft.com/en-us/windows/desktop/ProcThread/creating-processes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@vstinner If we're in __del__, presumably that means that nobody has a reference to the Popen object, right? In that case, we can let it be cleaned up. Closing the handle does not terminate the process - it'll just keep running and we can't get back to it through that particular handle (but we could use OpenProcess with the PID to get a new handle later).

Comment thread Lib/subprocess.py Outdated
@bedevere-bot

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@efiop
efiop force-pushed the fix-bpo-37380 branch 2 times, most recently from eb04016 to 80d8568 Compare June 25, 2019 12:50
Comment thread Lib/subprocess.py Outdated
Comment thread Lib/subprocess.py Outdated
Comment thread Lib/subprocess.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@vstinner If we're in __del__, presumably that means that nobody has a reference to the Popen object, right? In that case, we can let it be cleaned up. Closing the handle does not terminate the process - it'll just keep running and we can't get back to it through that particular handle (but we could use OpenProcess with the PID to get a new handle later).

Comment thread Lib/test/test_subprocess.py Outdated
@vstinner

Copy link
Copy Markdown
Member

Steve:

@vstinner If we're in del, presumably that means that nobody has a reference to the Popen object, right? In that case, we can let it be cleaned up. Closing the handle does not terminate the process - it'll just keep running and we can't get back to it through that particular handle (but we could use OpenProcess with the PID to get a new handle later).

My worry is that a badly written program may "leak" programs which continue to run in the background which can cause different issues. For example, you may not be able to remove the directory used by these background tasks since they have a handle open on these directories. It can cause various different issues.

The idea of adding a detatch() method to Popen was proposed once but didn't convince enough people to be implemented: https://bugs.python.org/issue27068

The purpose of the ResourceWarning is to help the developer to write more reliable softwares. It's just a matter of a handle which remains open longer than expected.

I'm talking about this warning:

$ cat x.py 
import subprocess, sys, gc
proc = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)'])
proc = None
gc.collect()

$ python3 -Wd x.py 
/usr/lib64/python3.7/subprocess.py:858: ResourceWarning: subprocess 12920 is still running
  ResourceWarning, source=self)
ResourceWarning: Enable tracemalloc to get the object allocation traceback

I would like to also get this warning on Windows.

@efiop

efiop commented Jun 25, 2019

Copy link
Copy Markdown
Contributor Author

Guys, big thanks for the reviews! I've adjusted my patch, so that it only re-defines _active and _cleanup for windows. I didn't notice right away that __del__ is actually already able to deal with _active being None, so that we don't need to introduce any changes to __del__ at all 🎉 So we will still get the ResourceWarning for Windows.

Comment thread Lib/test/test_subprocess.py Outdated
Comment thread Lib/test/test_subprocess.py Outdated
Comment thread Lib/test/test_subprocess.py Outdated
Comment thread Lib/subprocess.py Outdated
@vstinner

Copy link
Copy Markdown
Member

I don't see where this PR explicitly closes the handle: https://bugs.python.org/issue37380#msg346568

Comment thread Lib/subprocess.py Outdated
@eryksun

eryksun commented Jun 25, 2019

Copy link
Copy Markdown
Contributor

As to Popen.__del__, I don't understand why we force scripts to always have to poll() in order to avoid a resource warning when warnings are enabled. Why can't we instead check whether self._internal_poll() is None instead of self.returncode is None, in order to quietly handle cases where the program has exited already? For example:

>>> p = subprocess.Popen('python -c ""')
>>> del p
C:\Program Files\Python37\lib\subprocess.py:858: ResourceWarning: subprocess 3180 is still running
  ResourceWarning, source=self)
ResourceWarning: Enable tracemalloc to get the object allocation traceback

The process exited. It is not running. The warning is wrong.

@eryksun

eryksun commented Jun 26, 2019

Copy link
Copy Markdown
Contributor

In a related issue to waiting on the child process, if someone wants to open an easy issue for this, we should not be creating self._waitpid_lock = threading.Lock() in Windows. It isn't used.

Comment thread Lib/test/test_subprocess.py Outdated
@vstinner

Copy link
Copy Markdown
Member

As to Popen.del, I don't understand why we force scripts to always have to poll() in order to avoid a resource warning when warnings are enabled. Why can't we instead check whether self._internal_poll() is None instead of self.returncode is None, in order to quietly handle cases where the program has exited already?

The purpose is to promote to handle explicitly resources, rather than relying on Python garbage collector to magically handle them: the garbage collection behaves differently on PyPy, and may not collect objects "immediately" depending if they are part of a reference cycle or not.

subprocess.Popen should "release" its resources when the process exit: close pipes, "close" the child processes, etc. I suggest to use "with popen: ..." since exit() closes pipes and waits until the child process completes.

@vstinner

Copy link
Copy Markdown
Member

In the case of Windows, I would expect that the handle can be closed as soon as the process completes. But it seems to be the case now: https://bugs.python.org/issue37380#msg346568

As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380#msg346333
[2] https://bugs.python.org/issue36067#msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
@efiop

efiop commented Jun 26, 2019

Copy link
Copy Markdown
Contributor Author

Adjusted the _cleanup comment. Please take a look.

Comment thread Lib/subprocess.py
# implicitly when the `Popen` instance is finalized (see `Handle.__del__`,
# which is calling `CloseHandle` as requested in [1]), so there is nothing
# for `_cleanup` to do.
#

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.

FYI, if bpo37410 is resolved, we'll also be explicitly closing _handle when returncode is set. This would be a significant change. Currently we can assume _handle is valid, but with the proposed change we'll have to acquire a lock and check whether self.returncode is None before code that sets it in _internal_poll, _wait, and terminate.

@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @efiop for the PR, and @vstinner for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8.
🐍🍒⛏🤖

@miss-islington

Copy link
Copy Markdown
Contributor

Thanks @efiop for the PR, and @vstinner for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7.
🐍🍒⛏🤖

@bedevere-bot

Copy link
Copy Markdown

GH-15706 is a backport of this pull request to the 3.7 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 5, 2019
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380GH-msg346333
[2] https://bugs.python.org/issue36067GH-msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
(cherry picked from commit 042821a)

Co-authored-by: Ruslan Kuprieiev <kupruser@gmail.com>
@bedevere-bot

Copy link
Copy Markdown

GH-15707 is a backport of this pull request to the 3.8 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 5, 2019
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380GH-msg346333
[2] https://bugs.python.org/issue36067GH-msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
(cherry picked from commit 042821a)

Co-authored-by: Ruslan Kuprieiev <kupruser@gmail.com>
vstinner pushed a commit that referenced this pull request Sep 6, 2019
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380GH-msg346333
[2] https://bugs.python.org/issue36067GH-msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
(cherry picked from commit 042821a)

Co-authored-by: Ruslan Kuprieiev <kupruser@gmail.com>
vstinner pushed a commit that referenced this pull request Sep 6, 2019
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380GH-msg346333
[2] https://bugs.python.org/issue36067GH-msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
(cherry picked from commit 042821a)

Co-authored-by: Ruslan Kuprieiev <kupruser@gmail.com>
lisroach pushed a commit to lisroach/cpython that referenced this pull request Sep 10, 2019
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380#msg346333
[2] https://bugs.python.org/issue36067#msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
DinoV pushed a commit to DinoV/cpython that referenced this pull request Jan 14, 2020
As noted by @eryksun in [1] and [2], using _cleanup and _active(in
__del__) is not necessary on Windows, since:

> Unlike Unix, a process in Windows doesn't have to be waited on by
> its parent to avoid a zombie. Keeping the handle open will actually
> create a zombie until the next _cleanup() call, which may be never
> if Popen() isn't called again.

This patch simply defines `subprocess._active` as `None`, for which we already
have the proper logic in place in `subprocess.Popen.__del__`, that prevents it
from trying to append the process to the `_active`. This patch also defines
`subprocess._cleanup` as a noop for Windows.

[1] https://bugs.python.org/issue37380#msg346333
[2] https://bugs.python.org/issue36067#msg336262

Signed-off-by: Ruslan Kuprieiev <ruslan@iterative.ai>
efiop added a commit to treeverse/dvc that referenced this pull request Nov 1, 2020
efiop added a commit to treeverse/dvc that referenced this pull request Nov 3, 2020
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.

7 participants