From a2948423c3b0614ad6286810187b39f04ac9f7e6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 Jan 2019 15:50:26 +0100 Subject: [PATCH 1/4] subprocess: close pipes/fds by using ExitStack Rationale: https://github.com/python/cpython/pull/11575#discussion_r250288394 --- Lib/subprocess.py | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1f6eb63b387f40..9b8103124f60ff 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -50,6 +50,7 @@ import sys import threading import warnings +import contextlib from time import monotonic as _time @@ -1072,28 +1073,28 @@ def _close_pipe_fds(self, # self._devnull is not always defined. devnull_fd = getattr(self, '_devnull', None) - if _mswindows: - if p2cread != -1: - p2cread.Close() - if c2pwrite != -1: - c2pwrite.Close() - if errwrite != -1: - errwrite.Close() - else: - if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: - os.close(p2cread) - if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: - os.close(c2pwrite) - if errwrite != -1 and errread != -1 and errwrite != devnull_fd: - os.close(errwrite) + with contextlib.ExitStack() as stack: + if _mswindows: + if p2cread != -1: + stack.callback(p2cread.Close) + if c2pwrite != -1: + stack.callback(c2pwrite.Close) + if errwrite != -1: + stack.callback(errwrite.Close) + else: + if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: + stack.callback(os.close, p2cread) + if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: + stack.callback(os.close, c2pwrite) + if errwrite != -1 and errread != -1 and errwrite != devnull_fd: + stack.callback(os.close, errwrite) - if devnull_fd is not None: - os.close(devnull_fd) + if devnull_fd is not None: + stack.callback(os.close, devnull_fd) # Prevent a double close of these handles/fds from __init__ on error. self._closed_child_pipe_fds = True - if _mswindows: # # Windows methods @@ -1113,8 +1114,10 @@ def _get_handles(self, stdin, stdout, stderr): p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: p2cread, _ = _winapi.CreatePipe(None, 0) - p2cread = Handle(p2cread) - _winapi.CloseHandle(_) + try: + p2cread = Handle(p2cread) + finally: + _winapi.CloseHandle(_) elif stdin == PIPE: p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) @@ -1131,8 +1134,10 @@ def _get_handles(self, stdin, stdout, stderr): c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) if c2pwrite is None: _, c2pwrite = _winapi.CreatePipe(None, 0) - c2pwrite = Handle(c2pwrite) - _winapi.CloseHandle(_) + try: + c2pwrite = Handle(c2pwrite) + finally: + _winapi.CloseHandle(_) elif stdout == PIPE: c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) From 93ac155b9b2405a79be232599b1abeb83891c734 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 Jan 2019 16:37:34 +0100 Subject: [PATCH 2/4] avoid try/finally block around CloseHandle() --- Lib/subprocess.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 9b8103124f60ff..875dc3f198eabc 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1114,10 +1114,7 @@ def _get_handles(self, stdin, stdout, stderr): p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: p2cread, _ = _winapi.CreatePipe(None, 0) - try: - p2cread = Handle(p2cread) - finally: - _winapi.CloseHandle(_) + p2cread = Handle(p2cread) elif stdin == PIPE: p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) @@ -1134,10 +1131,7 @@ def _get_handles(self, stdin, stdout, stderr): c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) if c2pwrite is None: _, c2pwrite = _winapi.CreatePipe(None, 0) - try: - c2pwrite = Handle(c2pwrite) - finally: - _winapi.CloseHandle(_) + c2pwrite = Handle(c2pwrite) elif stdout == PIPE: c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) From 685cef2970dfdaa157bab3149c63257041f71c0c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Sun, 27 Jan 2019 16:39:26 +0100 Subject: [PATCH 3/4] fix accidental removal of CloseHandle() calls --- Lib/subprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 875dc3f198eabc..0496b447e8ea03 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1115,6 +1115,7 @@ def _get_handles(self, stdin, stdout, stderr): if p2cread is None: p2cread, _ = _winapi.CreatePipe(None, 0) p2cread = Handle(p2cread) + _winapi.CloseHandle(_) elif stdin == PIPE: p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) @@ -1132,6 +1133,7 @@ def _get_handles(self, stdin, stdout, stderr): if c2pwrite is None: _, c2pwrite = _winapi.CreatePipe(None, 0) c2pwrite = Handle(c2pwrite) + _winapi.CloseHandle(_) elif stdout == PIPE: c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) From 65374c61215661de902343484b43a91f2afe67ff Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Tue, 29 Jan 2019 17:24:59 +0100 Subject: [PATCH 4/4] add NEWS entry --- .../next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst diff --git a/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst b/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst new file mode 100644 index 00000000000000..2a9588e745f42f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst @@ -0,0 +1,4 @@ +An ExitStack is now used internally within subprocess.POpen to clean up pipe +file handles. No behavior change in normal operation. But if closing one +handle were ever to cause an exception, the others will now be closed +instead of leaked. (patch by Giampaolo Rodola)