[Python-checkins] cpython (merge 3.4 -> 3.5): Issue #25441: asyncio: Raise error from drain() when socket is closed. (Merge

guido.van.rossum python-checkins at python.org
Mon Oct 19 16:01:14 EDT 2015


https://hg.python.org/cpython/rev/d30fbc55194d
changeset:   98792:d30fbc55194d
branch:      3.5
parent:      98789:40aba0cda7b6
parent:      98791:17f76258d11d
user:        Guido van Rossum <guido at python.org>
date:        Mon Oct 19 11:54:04 2015 -0700
summary:
  Issue #25441: asyncio: Raise error from drain() when socket is closed. (Merge 3.4->3.5)

files:
  Lib/asyncio/streams.py                |   9 +++
  Lib/test/test_asyncio/test_streams.py |  43 +++++++++++++++
  Misc/NEWS                             |   2 +
  3 files changed, 54 insertions(+), 0 deletions(-)


diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py
--- a/Lib/asyncio/streams.py
+++ b/Lib/asyncio/streams.py
@@ -301,6 +301,15 @@
             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
--- 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 @@
         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))
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -45,6 +45,8 @@
 Library
 -------
 
+- Issue #25441: asyncio: Raise error from drain() when socket is closed.
+
 - Issue #25410: Cleaned up and fixed minor bugs in C implementation of
   OrderedDict.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list