diff options
author | Guido van Rossum <guido@python.org> | 2015-10-19 18:49:30 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2015-10-19 18:49:30 (GMT) |
commit | c44ecdf687897a20f11d0c5212b51e8d31f6100a (patch) | |
tree | cd908b285d3249995ae3f34a0ae49cb093203253 | |
parent | 2bf91bf46cf5bf9913a126092a8d08bcd7a6157c (diff) | |
download | cpython-c44ecdf687897a20f11d0c5212b51e8d31f6100a.zip cpython-c44ecdf687897a20f11d0c5212b51e8d31f6100a.tar.gz cpython-c44ecdf687897a20f11d0c5212b51e8d31f6100a.tar.bz2 |
Issue #25441: asyncio: Raise error from drain() when socket is closed.
-rw-r--r-- | Lib/asyncio/streams.py | 9 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_streams.py | 43 | ||||
-rw-r--r-- | Misc/NEWS | 2 |
3 files changed, 54 insertions, 0 deletions
diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index bb9fb31..fb786ed 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -301,6 +301,15 @@ class StreamWriter: exc = self._reader.exception() if exc is not None: raise exc + if self._transport is not None: + if self._transport._closing: + # Yield to the event loop so connection_lost() may be + # called. Without this, _drain_helper() would return + # immediately, and code that calls + # write(...); yield from drain() + # in a loop would never call connection_lost(), so it + # would not see an error when the socket is closed. + yield yield from self._protocol._drain_helper() diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index d4a7582..1f375f0 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -2,8 +2,10 @@ import gc import os +import queue import socket import sys +import threading import unittest from unittest import mock try: @@ -632,6 +634,47 @@ os.close(fd) protocol = asyncio.StreamReaderProtocol(reader) self.assertIs(protocol._loop, self.loop) + def test_drain_raises(self): + # See http://bugs.python.org/issue25441 + + # This test should not use asyncio for the mock server; the + # whole point of the test is to test for a bug in drain() + # where it never gives up the event loop but the socket is + # closed on the server side. + + q = queue.Queue() + + def server(): + # Runs in a separate thread. + sock = socket.socket() + sock.bind(('localhost', 0)) + sock.listen(1) + addr = sock.getsockname() + q.put(addr) + clt, _ = sock.accept() + clt.close() + + @asyncio.coroutine + def client(host, port): + reader, writer = yield from asyncio.open_connection(host, port, loop=self.loop) + while True: + writer.write(b"foo\n") + yield from writer.drain() + + # Start the server thread and wait for it to be listening. + thread = threading.Thread(target=server) + thread.setDaemon(True) + thread.start() + addr = q.get() + + # Should not be stuck in an infinite loop. + with self.assertRaises((ConnectionResetError, BrokenPipeError)): + self.loop.run_until_complete(client(*addr)) + + # Clean up the thread. (Only on success; on failure, it may + # be stuck in accept().) + thread.join() + def test___repr__(self): stream = asyncio.StreamReader(loop=self.loop) self.assertEqual("<StreamReader>", repr(stream)) @@ -96,6 +96,8 @@ Core and Builtins Library ------- +- Issue #25441: asyncio: Raise error from drain() when socket is closed. + - Issue #25411: Improved Unicode support in SMTPHandler through better use of the email package. Thanks to user simon04 for the patch. |