diff --git a/CHANGES.rst b/CHANGES.rst index 3d0ec49826c..190f7717c43 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -39,7 +39,9 @@ CHANGES - Do not pause transport during set_parser stage #1211 - Lingering close doesn't terminate before timeout #1559 - + +- `setsockopt` may raise `OSError` exception if socket is closed already #1595 + - Lots of CancelledError when requests are interrupted #1565 - Allow users to specify what should happen to decoding errors diff --git a/aiohttp/parsers.py b/aiohttp/parsers.py index ef2e3071315..6b71a233173 100644 --- a/aiohttp/parsers.py +++ b/aiohttp/parsers.py @@ -243,16 +243,23 @@ def set_tcp_nodelay(self, value): value = bool(value) if self._tcp_nodelay == value: return - self._tcp_nodelay = value if self._socket is None: return if self._socket.family not in (socket.AF_INET, socket.AF_INET6): return - if self._tcp_cork: - self._tcp_cork = False - if CORK is not None: # pragma: no branch - self._socket.setsockopt(socket.IPPROTO_TCP, CORK, False) - self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value) + + # socket may be closed already, on windows OSError get raised + try: + if self._tcp_cork: + if CORK is not None: # pragma: no branch + self._socket.setsockopt(socket.IPPROTO_TCP, CORK, False) + self._tcp_cork = False + + self._socket.setsockopt( + socket.IPPROTO_TCP, socket.TCP_NODELAY, value) + self._tcp_nodelay = value + except OSError: + pass @property def tcp_cork(self): @@ -262,18 +269,21 @@ def set_tcp_cork(self, value): value = bool(value) if self._tcp_cork == value: return - self._tcp_cork = value if self._socket is None: return if self._socket.family not in (socket.AF_INET, socket.AF_INET6): return - if self._tcp_nodelay: - self._socket.setsockopt(socket.IPPROTO_TCP, - socket.TCP_NODELAY, - False) - self._tcp_nodelay = False - if CORK is not None: # pragma: no branch - self._socket.setsockopt(socket.IPPROTO_TCP, CORK, value) + + try: + if self._tcp_nodelay: + self._socket.setsockopt( + socket.IPPROTO_TCP, socket.TCP_NODELAY, False) + self._tcp_nodelay = False + if CORK is not None: # pragma: no branch + self._socket.setsockopt(socket.IPPROTO_TCP, CORK, value) + self._tcp_cork = value + except OSError: + pass class StreamProtocol(asyncio.streams.FlowControlMixin, asyncio.Protocol): diff --git a/tests/test_stream_writer.py b/tests/test_stream_writer.py index 62b9336c6a4..d13f636d594 100644 --- a/tests/test_stream_writer.py +++ b/tests/test_stream_writer.py @@ -41,6 +41,20 @@ def test_set_nodelay_no_change(loop): assert not s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) +def test_set_nodelay_exception(loop): + transport = mock.Mock() + s = mock.Mock() + s.setsockopt = mock.Mock() + s.family = (socket.AF_INET,) + s.setsockopt.side_effect = OSError + transport.get_extra_info.return_value = s + proto = mock.Mock() + reader = mock.Mock() + writer = StreamWriter(transport, proto, reader, loop) + writer.set_tcp_nodelay(True) + assert not writer.tcp_nodelay + + def test_set_nodelay_enable(loop): transport = mock.Mock() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -82,6 +96,7 @@ def test_set_nodelay_enable_ipv6(loop): @pytest.mark.skipif(not hasattr(socket, 'AF_UNIX'), reason="requires unix sockets") def test_set_nodelay_enable_unix(loop): + # do not set nodelay for unix socket transport = mock.Mock() s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) transport.get_extra_info.return_value = s @@ -89,7 +104,7 @@ def test_set_nodelay_enable_unix(loop): reader = mock.Mock() writer = StreamWriter(transport, proto, reader, loop) writer.set_tcp_nodelay(True) - assert writer.tcp_nodelay + assert not writer.tcp_nodelay def test_set_nodelay_enable_no_socket(loop): @@ -99,7 +114,7 @@ def test_set_nodelay_enable_no_socket(loop): reader = mock.Mock() writer = StreamWriter(transport, proto, reader, loop) writer.set_tcp_nodelay(True) - assert writer.tcp_nodelay + assert not writer.tcp_nodelay assert writer._socket is None @@ -182,7 +197,7 @@ def test_set_cork_enable_unix(loop): reader = mock.Mock() writer = StreamWriter(transport, proto, reader, loop) writer.set_tcp_cork(True) - assert writer.tcp_cork + assert not writer.tcp_cork @pytest.mark.skipif(CORK is None, reason="TCP_CORK or TCP_NOPUSH required") @@ -197,6 +212,19 @@ def test_set_cork_enable_no_socket(loop): assert writer._socket is None +def test_set_cork_exception(loop): + transport = mock.Mock() + s = mock.Mock() + s.setsockopt = mock.Mock() + s.family = (socket.AF_INET,) + s.setsockopt.side_effect = OSError + proto = mock.Mock() + reader = mock.Mock() + writer = StreamWriter(transport, proto, reader, loop) + writer.set_tcp_cork(True) + assert not writer.tcp_cork + + # cork and nodelay interference @pytest.mark.skipif(CORK is None, reason="TCP_CORK or TCP_NOPUSH required")