diff options
-rw-r--r-- | Lib/asyncio/constants.py | 5 | ||||
-rw-r--r-- | Lib/asyncio/selector_events.py | 21 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_base_events.py | 13 |
3 files changed, 30 insertions, 9 deletions
diff --git a/Lib/asyncio/constants.py b/Lib/asyncio/constants.py index 79c3b93..f9e1232 100644 --- a/Lib/asyncio/constants.py +++ b/Lib/asyncio/constants.py @@ -1,4 +1,7 @@ """Constants.""" - +# After the connection is lost, log warnings after this many write()s. LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 + +# Seconds to wait before retrying accept(). +ACCEPT_RETRY_DELAY = 1 diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index c296dcc..f7bc61a 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -5,6 +5,7 @@ also includes support for signal handling, see the unix_events sub-module. """ import collections +import errno import socket try: import ssl @@ -98,15 +99,23 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): try: conn, addr = sock.accept() conn.setblocking(False) - except (BlockingIOError, InterruptedError): + except (BlockingIOError, InterruptedError, ConnectionAbortedError): pass # False alarm. - except Exception: - # Bad error. Stop serving. - self.remove_reader(sock.fileno()) - sock.close() + except OSError as exc: # There's nowhere to send the error, so just log it. # TODO: Someone will want an error handler for this. - logger.exception('Accept failed') + if exc.errno in (errno.EMFILE, errno.ENFILE, + errno.ENOBUFS, errno.ENOMEM): + # Some platforms (e.g. Linux keep reporting the FD as + # ready, so we remove the read handler temporarily. + # We'll try again in a while. + logger.exception('Accept out of system resource (%s)', exc) + self.remove_reader(sock.fileno()) + self.call_later(constants.ACCEPT_RETRY_DELAY, + self._start_serving, + protocol_factory, sock, ssl, server) + else: + raise # The event loop will catch, log and ignore it. else: if ssl: self._make_ssl_transport( diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 9f36896..f4d16d9 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1,5 +1,6 @@ """Tests for base_events.py""" +import errno import logging import socket import time @@ -8,6 +9,7 @@ import unittest.mock from test.support import find_unused_port, IPV6_ENABLED from asyncio import base_events +from asyncio import constants from asyncio import events from asyncio import futures from asyncio import protocols @@ -585,11 +587,18 @@ class BaseEventLoopWithSelectorTests(unittest.TestCase): def test_accept_connection_exception(self, m_log): sock = unittest.mock.Mock() sock.fileno.return_value = 10 - sock.accept.side_effect = OSError() + sock.accept.side_effect = OSError(errno.EMFILE, 'Too many open files') + self.loop.remove_reader = unittest.mock.Mock() + self.loop.call_later = unittest.mock.Mock() self.loop._accept_connection(MyProto, sock) - self.assertTrue(sock.close.called) self.assertTrue(m_log.exception.called) + self.assertFalse(sock.close.called) + self.loop.remove_reader.assert_called_with(10) + self.loop.call_later.assert_called_with(constants.ACCEPT_RETRY_DELAY, + # self.loop._start_serving + unittest.mock.ANY, + MyProto, sock, None, None) if __name__ == '__main__': |