diff --git a/Doc/library/asyncore.rst b/Doc/library/asyncore.rst --- a/Doc/library/asyncore.rst +++ b/Doc/library/asyncore.rst @@ -104,7 +104,7 @@ .. method:: handle_read() - Called when the asynchronous loop detects that a :meth:`read` call on the + Called when the asynchronous loop detects that a :meth:`recv` call on the channel's socket will succeed. @@ -134,7 +134,9 @@ .. method:: handle_close() - Called when the socket is closed. + Called when the socket is closed by the remote end **and** :meth:`recv` + has been called, which should normally be done in :meth:`handle_read`. + Also called when an error condition has been met. .. method:: handle_error() diff --git a/Lib/asyncore.py b/Lib/asyncore.py --- a/Lib/asyncore.py +++ b/Lib/asyncore.py @@ -500,6 +500,9 @@ def handle_read(self): self.log_info('unhandled read event', 'warning') + # call recv() to have handle_close() be called when the remote end + # closes the connection + self.recv(1) def handle_write(self): self.log_info('unhandled write event', 'warning') diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py --- a/Lib/test/test_asyncore.py +++ b/Lib/test/test_asyncore.py @@ -302,7 +302,6 @@ try: sys.stdout = fp d.handle_expt() - d.handle_read() d.handle_write() d.handle_connect() finally: @@ -310,7 +309,6 @@ lines = fp.getvalue().splitlines() expected = ['warning: unhandled incoming priority event', - 'warning: unhandled read event', 'warning: unhandled write event', 'warning: unhandled connect event'] self.assertEqual(lines, expected) @@ -664,6 +662,35 @@ client = TestClient(self.family, server.address) self.loop_waiting_for_flag(client) + def test_handle_close_without_handle_read(self): + # Check that handle_close is called when the remote closes the socket, + # even when the dispatcher subclass has missed to implement + # handle_read. + + data = b'\0' * 128 + + class TestClient(BaseClient): + + def handle_connect(self): + self.send(data) + + def handle_close(self): + self.flag = True + self.close() + + class TestHandler(BaseTestHandler): + + def handle_read(self): + self.recv(len(data)) + self.close() + + def writable(self): + return False + + server = BaseServer(self.family, self.addr, TestHandler) + client = TestClient(self.family, server.address) + self.loop_waiting_for_flag(client) + @unittest.skipIf(sys.platform.startswith("sunos"), "OOB support is broken on Solaris") def test_handle_expt(self):