]> granicus.if.org Git - python/commitdiff
asyncio: Pause accepting whenever accept() returns certain errors. Fixes asyncio...
authorGuido van Rossum <guido@dropbox.com>
Fri, 1 Nov 2013 21:12:50 +0000 (14:12 -0700)
committerGuido van Rossum <guido@dropbox.com>
Fri, 1 Nov 2013 21:12:50 +0000 (14:12 -0700)
Lib/asyncio/constants.py
Lib/asyncio/selector_events.py
Lib/test/test_asyncio/test_base_events.py

index 79c3b931d221fde348e11720f2aefbbfb52a7bd8..f9e123281e2e3b204dbae6f09f6b22fe06ed06e5 100644 (file)
@@ -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
index c296dccb2d342bcddced7b945a63e7dd313a5248..f7bc61ac6a8870bd401aeb955360c6eadaaabfa4 100644 (file)
@@ -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(
index 9f36896fbfe4419713486458d659bc90e9ed4ad2..f4d16d9b0348368a44fe6b1e367185c9b104c460 100644 (file)
@@ -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__':