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

Commit eaf48c0

Browse files
committed
Switch subprocess stdin to a socketpair, attempting to fix CPython issue #19293 (AIX hang).
1 parent f6f3d77 commit eaf48c0

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

asyncio/unix_events.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
213213
self._loop = loop
214214
self._pipe = pipe
215215
self._fileno = pipe.fileno()
216+
mode = os.fstat(self._fileno).st_mode
217+
if not (stat.S_ISFIFO(mode) or stat.S_ISSOCK(mode)):
218+
raise ValueError("Pipe transport is for pipes/sockets only.")
216219
_set_nonblocking(self._fileno)
217220
self._protocol = protocol
218221
self._closing = False
@@ -275,21 +278,29 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
275278
self._loop = loop
276279
self._pipe = pipe
277280
self._fileno = pipe.fileno()
278-
if not stat.S_ISFIFO(os.fstat(self._fileno).st_mode):
279-
raise ValueError("Pipe transport is for pipes only.")
281+
mode = os.fstat(self._fileno).st_mode
282+
is_socket = stat.S_ISSOCK(mode)
283+
is_pipe = stat.S_ISFIFO(mode)
284+
if not (is_socket or is_pipe):
285+
raise ValueError("Pipe transport is for pipes/sockets only.")
280286
_set_nonblocking(self._fileno)
281287
self._protocol = protocol
282288
self._buffer = []
283289
self._conn_lost = 0
284290
self._closing = False # Set when close() or write_eof() called.
285-
self._loop.add_reader(self._fileno, self._read_ready)
291+
292+
# On AIX, the reader trick only works for sockets.
293+
# On other platforms it works for pipes and sockets.
294+
# (Exception: OS X 10.4? Issue #19294.)
295+
if is_socket or not sys.platform.startswith("aix"):
296+
self._loop.add_reader(self._fileno, self._read_ready)
286297

287298
self._loop.call_soon(self._protocol.connection_made, self)
288299
if waiter is not None:
289300
self._loop.call_soon(waiter.set_result, None)
290301

291302
def _read_ready(self):
292-
# pipe was closed by peer
303+
# Pipe was closed by peer.
293304
self._close()
294305

295306
def write(self, data):
@@ -435,8 +446,15 @@ def __init__(self, loop, protocol, args, shell,
435446
self._loop = loop
436447

437448
self._pipes = {}
449+
stdin_w = None
438450
if stdin == subprocess.PIPE:
439451
self._pipes[STDIN] = None
452+
# Use a socket pair for stdin, since not all platforms
453+
# support selecting read events on the write end of a
454+
# socket (which we use in order to detect closing of the
455+
# other end). Notably this is needed on AIX, and works
456+
# just fine on other platforms.
457+
stdin, stdin_w = self._loop._socketpair()
440458
if stdout == subprocess.PIPE:
441459
self._pipes[STDOUT] = None
442460
if stderr == subprocess.PIPE:
@@ -448,6 +466,9 @@ def __init__(self, loop, protocol, args, shell,
448466
self._proc = subprocess.Popen(
449467
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
450468
universal_newlines=False, bufsize=bufsize, **kwargs)
469+
if stdin_w is not None:
470+
stdin.close()
471+
self._proc.stdin = open(stdin_w.detach(), 'rb', buffering=bufsize)
451472
self._extra['subprocess'] = self._proc
452473

453474
def close(self):

tests/test_unix_events.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ def setUp(self):
312312
fcntl_patcher.start()
313313
self.addCleanup(fcntl_patcher.stop)
314314

315+
fstat_patcher = unittest.mock.patch('os.fstat')
316+
m_fstat = fstat_patcher.start()
317+
st = unittest.mock.Mock()
318+
st.st_mode = stat.S_IFIFO
319+
m_fstat.return_value = st
320+
self.addCleanup(fstat_patcher.stop)
321+
315322
def test_ctor(self):
316323
tr = unix_events._UnixReadPipeTransport(
317324
self.loop, self.pipe, self.protocol)

0 commit comments

Comments
 (0)