Kaydet (Commit) e76cb435 authored tarafından Victor Stinner's avatar Victor Stinner Kaydeden (comit) GitHub

[3.6] bpo-30121: Fix debug assert in subprocess on Windows (#1224) (#3173)

* bpo-30121: Fix debug assert in subprocess on Windows (#1224)

* bpo-30121: Fix debug assert in subprocess on Windows

This is caused by closing HANDLEs using os.close which is for CRT file
descriptors and not for HANDLEs.

* bpo-30121: Suppress debug assertion in test_subprocess when ran directly

(cherry picked from commit 4d385172)

* Add test_subprocess.test_nonexisting_with_pipes() (#3133)

bpo-30121: Test the Popen failure when Popen was created with pipes.
Create also NONEXISTING_CMD variable in test_subprocess.py.
(cherry picked from commit 9a83f651)
üst 12a3e343
...@@ -725,7 +725,10 @@ class Popen(object): ...@@ -725,7 +725,10 @@ class Popen(object):
to_close.append(self._devnull) to_close.append(self._devnull)
for fd in to_close: for fd in to_close:
try: try:
os.close(fd) if _mswindows and isinstance(fd, Handle):
fd.Close()
else:
os.close(fd)
except OSError: except OSError:
pass pass
...@@ -1005,6 +1008,9 @@ class Popen(object): ...@@ -1005,6 +1008,9 @@ class Popen(object):
errwrite.Close() errwrite.Close()
if hasattr(self, '_devnull'): if hasattr(self, '_devnull'):
os.close(self._devnull) os.close(self._devnull)
# Prevent a double close of these handles/fds from __init__
# on error.
self._closed_child_pipe_fds = True
# Retain the process handle, but close the thread handle # Retain the process handle, but close the thread handle
self._child_created = True self._child_created = True
......
...@@ -49,6 +49,8 @@ if mswindows: ...@@ -49,6 +49,8 @@ if mswindows:
else: else:
SETBINARY = '' SETBINARY = ''
NONEXISTING_CMD = ('nonexisting_i_hope',)
class BaseTestCase(unittest.TestCase): class BaseTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -1120,10 +1122,11 @@ class ProcessTestCase(BaseTestCase): ...@@ -1120,10 +1122,11 @@ class ProcessTestCase(BaseTestCase):
p.stdin.write(line) # expect that it flushes the line in text mode p.stdin.write(line) # expect that it flushes the line in text mode
os.close(p.stdin.fileno()) # close it without flushing the buffer os.close(p.stdin.fileno()) # close it without flushing the buffer
read_line = p.stdout.readline() read_line = p.stdout.readline()
try: with support.SuppressCrashReport():
p.stdin.close() try:
except OSError: p.stdin.close()
pass except OSError:
pass
p.stdin = None p.stdin = None
self.assertEqual(p.returncode, 0) self.assertEqual(p.returncode, 0)
self.assertEqual(read_line, expected) self.assertEqual(read_line, expected)
...@@ -1148,13 +1151,54 @@ class ProcessTestCase(BaseTestCase): ...@@ -1148,13 +1151,54 @@ class ProcessTestCase(BaseTestCase):
# 1024 times (each call leaked two fds). # 1024 times (each call leaked two fds).
for i in range(1024): for i in range(1024):
with self.assertRaises(OSError) as c: with self.assertRaises(OSError) as c:
subprocess.Popen(['nonexisting_i_hope'], subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
# ignore errors that indicate the command was not found # ignore errors that indicate the command was not found
if c.exception.errno not in (errno.ENOENT, errno.EACCES): if c.exception.errno not in (errno.ENOENT, errno.EACCES):
raise c.exception raise c.exception
def test_nonexisting_with_pipes(self):
# bpo-30121: Popen with pipes must close properly pipes on error.
# Previously, os.close() was called with a Windows handle which is not
# a valid file descriptor.
#
# Run the test in a subprocess to control how the CRT reports errors
# and to get stderr content.
try:
import msvcrt
msvcrt.CrtSetReportMode
except (AttributeError, ImportError):
self.skipTest("need msvcrt.CrtSetReportMode")
code = textwrap.dedent(f"""
import msvcrt
import subprocess
cmd = {NONEXISTING_CMD!r}
for report_type in [msvcrt.CRT_WARN,
msvcrt.CRT_ERROR,
msvcrt.CRT_ASSERT]:
msvcrt.CrtSetReportMode(report_type, msvcrt.CRTDBG_MODE_FILE)
msvcrt.CrtSetReportFile(report_type, msvcrt.CRTDBG_FILE_STDERR)
try:
subprocess.Popen([cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError:
pass
""")
cmd = [sys.executable, "-c", code]
proc = subprocess.Popen(cmd,
stderr=subprocess.PIPE,
universal_newlines=True)
with proc:
stderr = proc.communicate()[1]
self.assertEqual(stderr, "")
self.assertEqual(proc.returncode, 0)
@unittest.skipIf(threading is None, "threading required") @unittest.skipIf(threading is None, "threading required")
def test_double_close_on_error(self): def test_double_close_on_error(self):
# Issue #18851 # Issue #18851
...@@ -1167,7 +1211,7 @@ class ProcessTestCase(BaseTestCase): ...@@ -1167,7 +1211,7 @@ class ProcessTestCase(BaseTestCase):
t.start() t.start()
try: try:
with self.assertRaises(EnvironmentError): with self.assertRaises(EnvironmentError):
subprocess.Popen(['nonexisting_i_hope'], subprocess.Popen(NONEXISTING_CMD,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
...@@ -2433,7 +2477,7 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -2433,7 +2477,7 @@ class POSIXProcessTestCase(BaseTestCase):
# should trigger the wait() of p # should trigger the wait() of p
time.sleep(0.2) time.sleep(0.2)
with self.assertRaises(OSError) as c: with self.assertRaises(OSError) as c:
with subprocess.Popen(['nonexisting_i_hope'], with subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as proc: stderr=subprocess.PIPE) as proc:
pass pass
...@@ -2863,7 +2907,7 @@ class ContextManagerTests(BaseTestCase): ...@@ -2863,7 +2907,7 @@ class ContextManagerTests(BaseTestCase):
def test_invalid_args(self): def test_invalid_args(self):
with self.assertRaises((FileNotFoundError, PermissionError)) as c: with self.assertRaises((FileNotFoundError, PermissionError)) as c:
with subprocess.Popen(['nonexisting_i_hope'], with subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as proc: stderr=subprocess.PIPE) as proc:
pass pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment