Skip to content
This repository was archived by the owner on Nov 23, 2017. It is now read-only.

Commit 97d5e44

Browse files
committed
compatibility with Python 3.3
1 parent a84da85 commit 97d5e44

File tree

3 files changed

+305
-4
lines changed

3 files changed

+305
-4
lines changed

asyncio/tmp_subprocess33.py

+289
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import builtins
2+
import errno
3+
import io
4+
import os
5+
import subprocess
6+
import warnings
7+
8+
import _posixsubprocess
9+
from subprocess import (PIPE, _PLATFORM_DEFAULT_CLOSE_FDS, _create_pipe,
10+
_cleanup, _eintr_retry_call, )
11+
12+
mswindows = msvcrt = False
13+
14+
15+
# Popen for python 3.3
16+
class _Popen(subprocess.Popen):
17+
def __init__(self, args, bufsize=-1, executable=None,
18+
stdin=None, stdout=None, stderr=None,
19+
preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
20+
shell=False, cwd=None, env=None, universal_newlines=False,
21+
startupinfo=None, creationflags=0,
22+
restore_signals=True, start_new_session=False,
23+
pass_fds=()):
24+
"""Create new Popen instance."""
25+
_cleanup()
26+
27+
self._input = None
28+
self._communication_started = False
29+
if bufsize is None:
30+
bufsize = -1 # Restore default
31+
if not isinstance(bufsize, int):
32+
raise TypeError("bufsize must be an integer")
33+
34+
if mswindows:
35+
if preexec_fn is not None:
36+
raise ValueError("preexec_fn is not supported on Windows "
37+
"platforms")
38+
any_stdio_set = (stdin is not None or stdout is not None or
39+
stderr is not None)
40+
if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
41+
if any_stdio_set:
42+
close_fds = False
43+
else:
44+
close_fds = True
45+
elif close_fds and any_stdio_set:
46+
raise ValueError(
47+
"close_fds is not supported on Windows platforms"
48+
" if you redirect stdin/stdout/stderr")
49+
else:
50+
# POSIX
51+
if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
52+
close_fds = True
53+
if pass_fds and not close_fds:
54+
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
55+
close_fds = True
56+
if startupinfo is not None:
57+
raise ValueError("startupinfo is only supported on Windows "
58+
"platforms")
59+
if creationflags != 0:
60+
raise ValueError("creationflags is only supported on Windows "
61+
"platforms")
62+
63+
self.args = args
64+
self.stdin = None
65+
self.stdout = None
66+
self.stderr = None
67+
self.pid = None
68+
self.returncode = None
69+
self.universal_newlines = universal_newlines
70+
71+
# Input and output objects. The general principle is like
72+
# this:
73+
#
74+
# Parent Child
75+
# ------ -----
76+
# p2cwrite ---stdin---> p2cread
77+
# c2pread <--stdout--- c2pwrite
78+
# errread <--stderr--- errwrite
79+
#
80+
# On POSIX, the child objects are file descriptors. On
81+
# Windows, these are Windows file handles. The parent objects
82+
# are file descriptors on both platforms. The parent objects
83+
# are -1 when not using PIPEs. The child objects are -1
84+
# when not redirecting.
85+
86+
(p2cread, p2cwrite,
87+
c2pread, c2pwrite,
88+
errread, errwrite) = self._get_handles(stdin, stdout, stderr)
89+
90+
# We wrap OS handles *before* launching the child, otherwise a
91+
# quickly terminating child could make our fds unwrappable
92+
# (see #8458).
93+
94+
if mswindows:
95+
if p2cwrite != -1:
96+
p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0)
97+
if c2pread != -1:
98+
c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0)
99+
if errread != -1:
100+
errread = msvcrt.open_osfhandle(errread.Detach(), 0)
101+
102+
if p2cwrite != -1:
103+
self.stdin = io.open(p2cwrite, 'wb', bufsize)
104+
if universal_newlines:
105+
self.stdin = io.TextIOWrapper(self.stdin, write_through=True)
106+
if c2pread != -1:
107+
self.stdout = io.open(c2pread, 'rb', bufsize)
108+
if universal_newlines:
109+
self.stdout = io.TextIOWrapper(self.stdout)
110+
if errread != -1:
111+
self.stderr = io.open(errread, 'rb', bufsize)
112+
if universal_newlines:
113+
self.stderr = io.TextIOWrapper(self.stderr)
114+
115+
self._child_pipes_to_close = set()
116+
if stdin == PIPE:
117+
self._child_pipes_to_close.add(p2cread)
118+
if stdout == PIPE:
119+
self._child_pipes_to_close.add(c2pwrite)
120+
if stderr == PIPE:
121+
self._child_pipes_to_close.add(errwrite)
122+
if hasattr(self, '_devnull'):
123+
self._child_pipes_to_close.add(self._devnull)
124+
125+
try:
126+
self._execute_child(args, executable, preexec_fn, close_fds,
127+
pass_fds, cwd, env,
128+
startupinfo, creationflags, shell,
129+
p2cread, p2cwrite,
130+
c2pread, c2pwrite,
131+
errread, errwrite,
132+
restore_signals, start_new_session)
133+
except:
134+
# Cleanup if the child failed starting.
135+
self._cleanup_on_exec_failure()
136+
raise
137+
138+
def _cleanup_on_exec_failure(self):
139+
for f in filter(None, (self.stdin, self.stdout, self.stderr)):
140+
try:
141+
f.close()
142+
except OSError:
143+
pass # Ignore EBADF or other errors.
144+
145+
for fd in self._child_pipes_to_close:
146+
try:
147+
os.close(fd)
148+
except OSError:
149+
pass
150+
151+
self._child_pipes_to_close.clear()
152+
153+
if True: # XXX if unix
154+
def _execute_child(self, args, executable, preexec_fn, close_fds,
155+
pass_fds, cwd, env,
156+
startupinfo, creationflags, shell,
157+
p2cread, p2cwrite,
158+
c2pread, c2pwrite,
159+
errread, errwrite,
160+
restore_signals, start_new_session):
161+
"""Execute program (POSIX version)"""
162+
163+
if isinstance(args, (str, bytes)):
164+
args = [args]
165+
else:
166+
args = list(args)
167+
168+
if shell:
169+
args = ["/bin/sh", "-c"] + args
170+
if executable:
171+
args[0] = executable
172+
173+
if executable is None:
174+
executable = args[0]
175+
orig_executable = executable
176+
177+
# For transferring possible exec failure from child to parent.
178+
# Data format: "exception name:hex errno:description"
179+
# Pickle is not used; it is complex and involves memory allocation.
180+
errpipe_read, errpipe_write = self._get_exec_err_pipe()
181+
try:
182+
try:
183+
# We must avoid complex work that could involve
184+
# malloc or free in the child process to avoid
185+
# potential deadlocks, thus we do all this here.
186+
# and pass it to fork_exec()
187+
188+
if env is not None:
189+
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
190+
for k, v in env.items()]
191+
else:
192+
env_list = None # Use execv instead of execve.
193+
executable = os.fsencode(executable)
194+
if os.path.dirname(executable):
195+
executable_list = (executable,)
196+
else:
197+
# This matches the behavior of os._execvpe().
198+
executable_list = tuple(
199+
os.path.join(os.fsencode(dir), executable)
200+
for dir in os.get_exec_path(env))
201+
fds_to_keep = set(pass_fds)
202+
fds_to_keep.add(errpipe_write)
203+
self.pid = _posixsubprocess.fork_exec(
204+
args, executable_list,
205+
close_fds, sorted(fds_to_keep), cwd, env_list,
206+
p2cread, p2cwrite, c2pread, c2pwrite,
207+
errread, errwrite,
208+
errpipe_read, errpipe_write,
209+
restore_signals, start_new_session, preexec_fn)
210+
self._child_created = True
211+
finally:
212+
# be sure the FD is closed no matter what
213+
os.close(errpipe_write)
214+
215+
# self._devnull is not always defined.
216+
devnull_fd = getattr(self, '_devnull', None)
217+
to_close = set()
218+
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
219+
to_close.add(p2cread)
220+
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
221+
to_close.add(c2pwrite)
222+
if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
223+
to_close.add(errwrite)
224+
if devnull_fd is not None:
225+
to_close.add(devnull_fd)
226+
for fd in to_close:
227+
os.close(fd)
228+
# Prevent a double close of these fds from __init__ on error.
229+
self._child_pipes_to_close.remove(fd)
230+
except:
231+
os.close(errpipe_read)
232+
raise
233+
234+
self._wait_exec_done(orig_executable, cwd, errpipe_read)
235+
236+
def _get_exec_err_pipe(self):
237+
return _create_pipe()
238+
239+
def _wait_exec_done(self, orig_executable, cwd, errpipe_read):
240+
assert errpipe_read is not None
241+
try:
242+
# Wait for exec to fail or succeed; possibly raising an
243+
# exception (limited in size)
244+
errpipe_data = bytearray()
245+
while True:
246+
part = _eintr_retry_call(os.read, errpipe_read, 50000)
247+
errpipe_data += part
248+
if not part or len(errpipe_data) > 50000:
249+
break
250+
finally:
251+
# be sure the FD is closed no matter what
252+
os.close(errpipe_read)
253+
254+
if errpipe_data:
255+
self._check_exec_result(orig_executable, cwd, errpipe_data)
256+
257+
def _check_exec_result(self, orig_executable, cwd, errpipe_data):
258+
try:
259+
_eintr_retry_call(os.waitpid, self.pid, 0)
260+
except OSError as e:
261+
if e.errno != errno.ECHILD:
262+
raise
263+
try:
264+
exception_name, hex_errno, err_msg = (
265+
errpipe_data.split(b':', 2))
266+
except ValueError:
267+
exception_name = b'RuntimeError'
268+
hex_errno = b'0'
269+
err_msg = (b'Bad exception data from child: ' +
270+
repr(errpipe_data))
271+
child_exception_type = getattr(
272+
builtins, exception_name.decode('ascii'),
273+
RuntimeError)
274+
err_msg = err_msg.decode(errors="surrogatepass")
275+
if issubclass(child_exception_type, OSError) and hex_errno:
276+
errno_num = int(hex_errno, 16)
277+
child_exec_never_called = (err_msg == "noexec")
278+
if child_exec_never_called:
279+
err_msg = ""
280+
if errno_num != 0:
281+
err_msg = os.strerror(errno_num)
282+
if errno_num == errno.ENOENT:
283+
if child_exec_never_called:
284+
# The error must be from chdir(cwd).
285+
err_msg += ': ' + repr(cwd)
286+
else:
287+
err_msg += ': ' + repr(orig_executable)
288+
raise child_exception_type(errno_num, err_msg)
289+
raise child_exception_type(err_msg)

asyncio/unix_events.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
from .log import logger
2626

2727
# XXX temporary: a monkey-patched subprocess.Popen
28-
from . import tmp_subprocess
28+
if compat.PY34:
29+
from . import tmp_subprocess
30+
else:
31+
# Python 3.3 has a different version of Popen
32+
from . import tmp_subprocess33 as tmp_subprocess
2933

3034

3135
__all__ = ['SelectorEventLoop',
@@ -660,6 +664,7 @@ def _cleanup_on_exec_failure(self):
660664

661665
def _get_exec_err_pipe(self):
662666
errpipe_read, errpipe_write = self._loop._socketpair()
667+
_set_inheritable(errpipe_write.fileno(), False)
663668
return errpipe_read.detach(), errpipe_write.detach()
664669

665670
def _wait_exec_done(self, orig_executable, cwd, errpipe_read):

tests/test_subprocess.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import multiprocessing
22
import signal
33
import sys
4-
import time
54
import unittest
65
import warnings
76
from unittest import mock
8-
from subprocess import SubprocessError
97

108
import asyncio
119
from asyncio import base_subprocess
10+
from asyncio import compat
1211
from asyncio import subprocess
1312
from asyncio import test_utils
1413
try:
@@ -174,9 +173,17 @@ def raise_exception():
174173
args = PROGRAM_BLOCKED
175174
create = asyncio.create_subprocess_exec(
176175
*args, preexec_fn=raise_exception, loop=self.loop)
177-
with self.assertRaises(SubprocessError):
176+
with self.assertRaises(Exception) as ctx:
178177
self.loop.run_until_complete(create)
179178

179+
if compat.PY34:
180+
from subprocess import SubprocessError
181+
self.assertIsInstance(ctx.exception, SubprocessError)
182+
else:
183+
self.assertIsInstance(ctx.exception, RuntimeError)
184+
self.assertEqual("Exception occurred in preexec_fn.",
185+
str(ctx.exception))
186+
180187
def test_cancel_during_preexec(self):
181188
lock = multiprocessing.Lock()
182189
lock.acquire()

0 commit comments

Comments
 (0)