From fe9ebe4f4ff848cda1d4a5e398354b3be3fb984e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 24 Jul 2014 19:15:00 +0200 Subject: Issue #16133: The asynchat.async_chat.handle_read() method now ignores socket.error() exceptions with blocking I/O errors: EAGAIN, EALREADY, EINPROGRESS, or EWOULDBLOCK. Initial patch written by Xavier de Gaye. --- Doc/library/asyncore.rst | 5 +++++ Lib/asynchat.py | 9 ++++++++- Lib/test/test_asynchat.py | 32 +++++++++++++++++++++++++++++++- Misc/NEWS | 4 ++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Doc/library/asyncore.rst b/Doc/library/asyncore.rst index 70ca8e7..31cd497 100644 --- a/Doc/library/asyncore.rst +++ b/Doc/library/asyncore.rst @@ -193,6 +193,11 @@ any that have been added to the map during asynchronous service) is closed. Read at most *buffer_size* bytes from the socket's remote end-point. An empty string implies that the channel has been closed from the other end. + Note that :meth:`recv` may raise :exc:`socket.error` with + :data:`~errno.EAGAIN` or :data:`~errno.EWOULDBLOCK`, even though + :func:`select.select` or :func:`select.poll` has reported the socket + ready for reading. + .. method:: listen(backlog) diff --git a/Lib/asynchat.py b/Lib/asynchat.py index 911833d..57459a0 100644 --- a/Lib/asynchat.py +++ b/Lib/asynchat.py @@ -46,12 +46,17 @@ method) up to the terminator, and then control will be returned to you - by calling your self.found_terminator() method. """ -import socket import asyncore +import errno +import socket from collections import deque from sys import py3kwarning from warnings import filterwarnings, catch_warnings +_BLOCKING_IO_ERRORS = (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, + errno.EWOULDBLOCK) + + class async_chat (asyncore.dispatcher): """This is an abstract class. You must derive from this class, and add the two methods collect_incoming_data() and found_terminator()""" @@ -109,6 +114,8 @@ class async_chat (asyncore.dispatcher): try: data = self.recv (self.ac_in_buffer_size) except socket.error, why: + if why.args[0] in _BLOCKING_IO_ERRORS: + return self.handle_error() return diff --git a/Lib/test/test_asynchat.py b/Lib/test/test_asynchat.py index c81ec0d..9c4c714 100644 --- a/Lib/test/test_asynchat.py +++ b/Lib/test/test_asynchat.py @@ -1,6 +1,10 @@ # test asynchat -import asyncore, asynchat, socket, time +import errno +import asyncore +import asynchat +import socket +import time import unittest import sys from test import test_support @@ -235,6 +239,31 @@ class TestAsynchat(unittest.TestCase): class TestAsynchat_WithPoll(TestAsynchat): usepoll = True + +class TestAsynchatMocked(unittest.TestCase): + def test_blockingioerror(self): + # Issue #16133: handle_read() must ignore blocking I/O errors like + # EAGAIN + class fake_socket: + def fileno(self): + return 0 + + def recv(self, size): + raise socket.error(errno.EAGAIN, "EAGAIN") + + class MyChat(asynchat.async_chat): + def handle_error(self): + raise Exception("error") + + sock = fake_socket() + dispatcher = MyChat() + dispatcher.set_socket(sock) + self.addCleanup(dispatcher.del_channel) + + # must not call handle_error() + dispatcher.handle_read() + + class TestHelperFunctions(unittest.TestCase): def test_find_prefix_at_end(self): self.assertEqual(asynchat.find_prefix_at_end("qwerty\r", "\r\n"), 1) @@ -267,6 +296,7 @@ class TestFifo(unittest.TestCase): def test_main(verbose=None): test_support.run_unittest(TestAsynchat, TestAsynchat_WithPoll, + TestAsynchatMocked, TestHelperFunctions, TestFifo) if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index f1c9ab3..4cfa3c2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,10 @@ Core and Builtins Library ------- +- Issue #16133: The asynchat.async_chat.handle_read() method now ignores + socket.error() exceptions with blocking I/O errors: EAGAIN, EALREADY, + EINPROGRESS, or EWOULDBLOCK. + - Issue #1730136: Fix the comparison between a tkFont.Font and an object of another kind. -- cgit v0.12