diff --git a/eventlet/convenience.py b/eventlet/convenience.py index 560de23..7453ff8 100644 --- a/eventlet/convenience.py +++ b/eventlet/convenience.py @@ -3,6 +3,7 @@ import warnings from eventlet import greenpool from eventlet import greenthread +from eventlet import support from eventlet.green import socket from eventlet.support import greenlets as greenlet @@ -26,6 +27,10 @@ class ReuseRandomPortWarning(Warning): pass +class ReusePortUnavailableWarning(Warning): + pass + + def listen(addr, family=socket.AF_INET, backlog=50, reuse_addr=True, reuse_port=None): """Convenience function for opening server sockets. This socket can be used in :func:`~eventlet.serve` or a custom ``accept()`` loop. @@ -55,7 +60,20 @@ def listen(addr, family=socket.AF_INET, backlog=50, reuse_addr=True, reuse_port= reuse_port = True if reuse_port and hasattr(socket, 'SO_REUSEPORT'): # NOTE(zhengwei): linux kernel >= 3.9 - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except OSError as ex: + if support.get_errno(ex) in (22, 92): + # A famous platform defines unsupported socket option. + # https://github.com/eventlet/eventlet/issues/380 + # https://github.com/eventlet/eventlet/issues/418 + warnings.warn( + '''socket.SO_REUSEPORT is defined but not supported. + On Windows: known bug, wontfix. + On other systems: please comment in the issue linked below. + More information: https://github.com/eventlet/eventlet/issues/380''', + ReusePortUnavailableWarning, stacklevel=3) + sock.bind(addr) sock.listen(backlog) return sock diff --git a/tests/convenience_test.py b/tests/convenience_test.py index 6f996fe..32e74e3 100644 --- a/tests/convenience_test.py +++ b/tests/convenience_test.py @@ -6,6 +6,7 @@ from eventlet import convenience, debug from eventlet.green import socket from eventlet.support import six import tests +import tests.mock certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') @@ -167,3 +168,29 @@ def test_reuse_random_port_warning(): eventlet.listen(('localhost', 0), reuse_port=True).close() assert len(w) == 1 assert issubclass(w[0].category, convenience.ReuseRandomPortWarning) + + +@tests.skip_unless(hasattr(socket, 'SO_REUSEPORT')) +def test_reuseport_oserror(): + # https://github.com/eventlet/eventlet/issues/380 + # https://github.com/eventlet/eventlet/issues/418 + err22 = OSError(22, 'Invalid argument') + + sock1 = eventlet.listen(('localhost', 0)) + addr = sock1.getsockname() + sock1.close() + + original_socket_init = socket.socket.__init__ + + def patched(self, *a, **kw): + original_socket_init(self, *a, **kw) + self.setsockopt = tests.mock.Mock(side_effect=err22) + + with warnings.catch_warnings(record=True) as w: + try: + socket.socket.__init__ = patched + eventlet.listen(addr, reuse_addr=False, reuse_port=True).close() + finally: + socket.socket.__init__ = original_socket_init + assert len(w) == 1 + assert issubclass(w[0].category, convenience.ReusePortUnavailableWarning)