From ca2e0a48cf0dd663ba7457dc6bce60988cdfd56c Mon Sep 17 00:00:00 2001
From: Yury Selivanov <yury@magic.io>
Date: Sat, 11 Jun 2016 11:19:47 -0400
Subject: Issue #27223: aio: Fix _read_ready and _write_ready to respect
 _conn_lost.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Patch by Łukasz Langa.
---
 Lib/asyncio/selector_events.py                | 11 ++++++++++
 Lib/test/test_asyncio/test_selector_events.py | 30 ++++++++++++---------------
 Misc/NEWS                                     |  6 +++++-
 3 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index fb7ab21..9564d01 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -569,6 +569,7 @@ class _SelectorTransport(transports._FlowControlMixin,
         self._loop.remove_reader(self._sock_fd)
         if not self._buffer:
             self._conn_lost += 1
+            self._loop.remove_writer(self._sock_fd)
             self._loop.call_soon(self._call_connection_lost, None)
 
     # On Python 3.3 and older, objects with a destructor part of a reference
@@ -662,6 +663,8 @@ class _SelectorSocketTransport(_SelectorTransport):
             logger.debug("%r resumes reading", self)
 
     def _read_ready(self):
+        if self._conn_lost:
+            return
         try:
             data = self._sock.recv(self.max_size)
         except (BlockingIOError, InterruptedError):
@@ -721,6 +724,8 @@ class _SelectorSocketTransport(_SelectorTransport):
     def _write_ready(self):
         assert self._buffer, 'Data should not be empty'
 
+        if self._conn_lost:
+            return
         try:
             n = self._sock.send(self._buffer)
         except (BlockingIOError, InterruptedError):
@@ -891,6 +896,8 @@ class _SelectorSslTransport(_SelectorTransport):
             logger.debug("%r resumes reading", self)
 
     def _read_ready(self):
+        if self._conn_lost:
+            return
         if self._write_wants_read:
             self._write_wants_read = False
             self._write_ready()
@@ -923,6 +930,8 @@ class _SelectorSslTransport(_SelectorTransport):
                     self.close()
 
     def _write_ready(self):
+        if self._conn_lost:
+            return
         if self._read_wants_write:
             self._read_wants_write = False
             self._read_ready()
@@ -1000,6 +1009,8 @@ class _SelectorDatagramTransport(_SelectorTransport):
         return sum(len(data) for data, _ in self._buffer)
 
     def _read_ready(self):
+        if self._conn_lost:
+            return
         try:
             data, addr = self._sock.recvfrom(self.max_size)
         except (BlockingIOError, InterruptedError):
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
index 8ad5535..5dc6ff8 100644
--- a/Lib/test/test_asyncio/test_selector_events.py
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -1087,17 +1087,6 @@ class SelectorSocketTransportTests(test_utils.TestCase):
                                    err,
                                    'Fatal write error on socket transport')
 
-    @mock.patch('asyncio.base_events.logger')
-    def test_write_ready_exception_and_close(self, m_log):
-        self.sock.send.side_effect = OSError()
-        remove_writer = self.loop.remove_writer = mock.Mock()
-
-        transport = self.socket_transport()
-        transport.close()
-        transport._buffer.extend(b'data')
-        transport._write_ready()
-        remove_writer.assert_called_with(self.sock_fd)
-
     def test_write_eof(self):
         tr = self.socket_transport()
         self.assertTrue(tr.can_write_eof())
@@ -1121,6 +1110,14 @@ class SelectorSocketTransportTests(test_utils.TestCase):
         self.sock.shutdown.assert_called_with(socket.SHUT_WR)
         tr.close()
 
+    @mock.patch('asyncio.base_events.logger')
+    def test_transport_close_remove_writer(self, m_log):
+        remove_writer = self.loop.remove_writer = mock.Mock()
+
+        transport = self.socket_transport()
+        transport.close()
+        remove_writer.assert_called_with(self.sock_fd)
+
 
 @unittest.skipIf(ssl is None, 'No ssl module')
 class SelectorSslTransportTests(test_utils.TestCase):
@@ -1175,7 +1172,7 @@ class SelectorSslTransportTests(test_utils.TestCase):
         self.sslsock.do_handshake.side_effect = exc
         with test_utils.disable_logger():
             waiter = asyncio.Future(loop=self.loop)
-            transport = self.ssl_transport(waiter=waiter)
+            self.ssl_transport(waiter=waiter)
         self.assertTrue(waiter.done())
         self.assertIs(exc, waiter.exception())
         self.assertTrue(self.sslsock.close.called)
@@ -1374,20 +1371,19 @@ class SelectorSslTransportTests(test_utils.TestCase):
     def test_write_ready_send_closing(self):
         self.sslsock.send.return_value = 4
         transport = self._make_one()
-        transport.close()
         transport._buffer = list_to_buffer([b'data'])
+        transport.close()
         transport._write_ready()
-        self.assertFalse(self.loop.writers)
         self.protocol.connection_lost.assert_called_with(None)
 
     def test_write_ready_send_closing_empty_buffer(self):
         self.sslsock.send.return_value = 4
+        call_soon = self.loop.call_soon = mock.Mock()
         transport = self._make_one()
-        transport.close()
         transport._buffer = list_to_buffer()
+        transport.close()
         transport._write_ready()
-        self.assertFalse(self.loop.writers)
-        self.protocol.connection_lost.assert_called_with(None)
+        call_soon.assert_called_with(transport._call_connection_lost, None)
 
     def test_write_ready_send_retry(self):
         transport = self._make_one()
diff --git a/Misc/NEWS b/Misc/NEWS
index 10d7cae..c40fc4b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -520,6 +520,10 @@ Library
 
 - Issue #27041: asyncio: Add loop.create_future method
 
+- Issue #27223: asyncio: Fix _read_ready and _write_ready to respect
+  _conn_lost.
+  Patch by Łukasz Langa.
+
 IDLE
 ----
 
@@ -709,7 +713,7 @@ Tools/Demos
 Misc
 ----
 
-- Issue #17500, and https://github.com/python/pythondotorg/issues/945: Remove 
+- Issue #17500, and https://github.com/python/pythondotorg/issues/945: Remove
   unused and outdated icons.
 
 
-- 
cgit v0.12

