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

Commit a30934a

Browse files
ronfasvetlov
authored andcommitted
Don't select for read on character devices in _UnixWritePipeTransport (#374)
* Don't select for read on character devices in _UnixWritePipeTransport _UnixWritePipeTransport selects for read on write pipes to detect when the remote end of the pipe is closed. This works for unidirectional FIFOs and dedicated socket pairs where nothing is written to the read side of the pair, but it can cause problems with devices or sockets where bidirectional I/O is being done. This commit changes _UnixWritePipeTransport to only select for read on sockets and FIFOs (on OSes which support that), and not on character devices. When connect_write_pipe() is used on those devices, end-of-file will need to be detected using a read pipe which accepts inputs from the device. No change is made to how sockets are handled, so passing in a socket used for bidirectional I/O to connect_write_pipe() is not supported. Async I/O on sockets should be performed using functions like BaseEventLoop.create_connection(). Socket pairs which are only used for undirectional I/O can be used here, though. * Add new unit test for bidirectional I/O on TTYs * Don't import tty module on Windows While the Windows release of Python seems to include the 'tty' module, attempting to import it fails with an error about not finding the 'termios' module. Since the tty tests here aren't run on Windows, though, there's no need to import tty there. This commit makes the import conditional on the platform.
1 parent bbe53f3 commit a30934a

File tree

2 files changed

+79
-4
lines changed

2 files changed

+79
-4
lines changed

asyncio/unix_events.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,10 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
422422
self._pipe = pipe
423423
self._fileno = pipe.fileno()
424424
mode = os.fstat(self._fileno).st_mode
425+
is_char = stat.S_ISCHR(mode)
426+
is_fifo = stat.S_ISFIFO(mode)
425427
is_socket = stat.S_ISSOCK(mode)
426-
if not (is_socket or
427-
stat.S_ISFIFO(mode) or
428-
stat.S_ISCHR(mode)):
428+
if not (is_char or is_fifo or is_socket):
429429
raise ValueError("Pipe transport is only for "
430430
"pipes, sockets and character devices")
431431
_set_nonblocking(self._fileno)
@@ -439,7 +439,7 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
439439
# On AIX, the reader trick (to be notified when the read end of the
440440
# socket is closed) only works for sockets. On other platforms it
441441
# works for pipes and sockets. (Exception: OS X 10.4? Issue #19294.)
442-
if is_socket or not sys.platform.startswith("aix"):
442+
if is_socket or (is_fifo and not sys.platform.startswith("aix")):
443443
# only start reading when connection_made() has been called
444444
self._loop.call_soon(self._loop.add_reader,
445445
self._fileno, self._read_ready)

tests/test_events.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from unittest import mock
2222
import weakref
2323

24+
if sys.platform != 'win32':
25+
import tty
2426

2527
import asyncio
2628
from asyncio import proactor_events
@@ -1626,6 +1628,79 @@ def reader(data):
16261628
self.loop.run_until_complete(proto.done)
16271629
self.assertEqual('CLOSED', proto.state)
16281630

1631+
@unittest.skipUnless(sys.platform != 'win32',
1632+
"Don't support pipes for Windows")
1633+
# select, poll and kqueue don't support character devices (PTY) on Mac OS X
1634+
# older than 10.6 (Snow Leopard)
1635+
@support.requires_mac_ver(10, 6)
1636+
def test_bidirectional_pty(self):
1637+
master, read_slave = os.openpty()
1638+
write_slave = os.dup(read_slave)
1639+
tty.setraw(read_slave)
1640+
1641+
slave_read_obj = io.open(read_slave, 'rb', 0)
1642+
read_proto = MyReadPipeProto(loop=self.loop)
1643+
read_connect = self.loop.connect_read_pipe(lambda: read_proto,
1644+
slave_read_obj)
1645+
read_transport, p = self.loop.run_until_complete(read_connect)
1646+
self.assertIs(p, read_proto)
1647+
self.assertIs(read_transport, read_proto.transport)
1648+
self.assertEqual(['INITIAL', 'CONNECTED'], read_proto.state)
1649+
self.assertEqual(0, read_proto.nbytes)
1650+
1651+
1652+
slave_write_obj = io.open(write_slave, 'wb', 0)
1653+
write_proto = MyWritePipeProto(loop=self.loop)
1654+
write_connect = self.loop.connect_write_pipe(lambda: write_proto,
1655+
slave_write_obj)
1656+
write_transport, p = self.loop.run_until_complete(write_connect)
1657+
self.assertIs(p, write_proto)
1658+
self.assertIs(write_transport, write_proto.transport)
1659+
self.assertEqual('CONNECTED', write_proto.state)
1660+
1661+
data = bytearray()
1662+
def reader(data):
1663+
chunk = os.read(master, 1024)
1664+
data += chunk
1665+
return len(data)
1666+
1667+
write_transport.write(b'1')
1668+
test_utils.run_until(self.loop, lambda: reader(data) >= 1, timeout=10)
1669+
self.assertEqual(b'1', data)
1670+
self.assertEqual(['INITIAL', 'CONNECTED'], read_proto.state)
1671+
self.assertEqual('CONNECTED', write_proto.state)
1672+
1673+
os.write(master, b'a')
1674+
test_utils.run_until(self.loop, lambda: read_proto.nbytes >= 1,
1675+
timeout=10)
1676+
self.assertEqual(['INITIAL', 'CONNECTED'], read_proto.state)
1677+
self.assertEqual(1, read_proto.nbytes)
1678+
self.assertEqual('CONNECTED', write_proto.state)
1679+
1680+
write_transport.write(b'2345')
1681+
test_utils.run_until(self.loop, lambda: reader(data) >= 5, timeout=10)
1682+
self.assertEqual(b'12345', data)
1683+
self.assertEqual(['INITIAL', 'CONNECTED'], read_proto.state)
1684+
self.assertEqual('CONNECTED', write_proto.state)
1685+
1686+
os.write(master, b'bcde')
1687+
test_utils.run_until(self.loop, lambda: read_proto.nbytes >= 5,
1688+
timeout=10)
1689+
self.assertEqual(['INITIAL', 'CONNECTED'], read_proto.state)
1690+
self.assertEqual(5, read_proto.nbytes)
1691+
self.assertEqual('CONNECTED', write_proto.state)
1692+
1693+
os.close(master)
1694+
1695+
read_transport.close()
1696+
self.loop.run_until_complete(read_proto.done)
1697+
self.assertEqual(
1698+
['INITIAL', 'CONNECTED', 'EOF', 'CLOSED'], read_proto.state)
1699+
1700+
write_transport.close()
1701+
self.loop.run_until_complete(write_proto.done)
1702+
self.assertEqual('CLOSED', write_proto.state)
1703+
16291704
def test_prompt_cancellation(self):
16301705
r, w = test_utils.socketpair()
16311706
r.setblocking(False)

0 commit comments

Comments
 (0)