Skip to content

Commit

Permalink
Fix #1955: Fix 100% CPU usage on HTTP GET and websocket connection ju…
Browse files Browse the repository at this point in the history
…st after it
  • Loading branch information
asvetlov committed Feb 1, 2018
1 parent 7557fdf commit 2fa858e
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Changelog

.. towncrier release notes start
2.3.10 (XXXX-XX-XX)
===================

- Fix 100% CPU usage on HTTP GET and websocket connection just after it (#1955)

2.3.9 (2018-01-16)
==================

Expand Down
5 changes: 4 additions & 1 deletion aiohttp/web_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ def keep_alive(self, val):
:param bool val: new state.
"""
self._keepalive = val
if self._keepalive_handle:
self._keepalive_handle.cancel()
self._keepalive_handle = None

def close(self):
"""Stop accepting new pipelinig messages and close
Expand Down Expand Up @@ -353,7 +356,7 @@ def log_exception(self, *args, **kw):
self.logger.exception(*args, **kw)

def _process_keepalive(self):
if self._force_close:
if self._force_close or not self._keepalive:
return

next = self._keepalive_time + self._keepalive_timeout
Expand Down
2 changes: 2 additions & 0 deletions aiohttp/web_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def _post_start(self, request, protocol, writer):
request._protocol, limit=2 ** 16, loop=self._loop)
request.protocol.set_parser(WebSocketReader(
self._reader, compress=self._compress))
# disable HTTP keepalive for WebSocket
request.protocol.keep_alive(False)

def can_prepare(self, request):
if self._writer is not None:
Expand Down
9 changes: 9 additions & 0 deletions tests/test_web_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@ def test_srv_keep_alive(srv):
assert not srv._keepalive


def test_srv_keep_alive_disable(srv):
handle = srv._keepalive_handle = mock.Mock()

srv.keep_alive(False)
assert not srv._keepalive
assert srv._keepalive_handle is None
handle.cancel.assert_called_with()


def test_slow_request(make_srv):
with pytest.warns(DeprecationWarning):
make_srv(slow_request_timeout=0.01)
Expand Down
29 changes: 29 additions & 0 deletions tests/test_web_websocket_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,3 +758,32 @@ def handler(request):
yield from ws.receive()

assert cancelled


@asyncio.coroutine
def test_websocket_disable_keepalive(loop, test_client):
@asyncio.coroutine
def handler(request):
ws = web.WebSocketResponse()
if not ws.can_prepare(request):
return web.Response(text='OK')
assert request.protocol._keepalive
yield from ws.prepare(request)
assert not request.protocol._keepalive
assert not request.protocol._keepalive_handle

yield from ws.send_str('OK')
yield from ws.close()
return ws

app = web.Application()
app.router.add_route('GET', '/', handler)
client = yield from test_client(app)

resp = yield from client.get('/')
txt = yield from resp.text()
assert txt == 'OK'

ws = yield from client.ws_connect('/')
data = yield from ws.receive_str()
assert data == 'OK'

0 comments on commit 2fa858e

Please sign in to comment.