Skip to content

Commit

Permalink
Prevent infinite hang on WebSocket response.close() (#1002) (#1084)
Browse files Browse the repository at this point in the history
* Prevent indefinite hang on WebSocket response.close() (#1002)

 * The close() method now checks the time elapsed since the original
   starting time as well as the timeout for each iteration.

 * Now close() should wait no more than at most twice of the timeout
   given to WebSocketResponse.

* Fix typo and flake formatting errors.
  • Loading branch information
achimnol authored and asvetlov committed Aug 15, 2016
1 parent 0f7cc5e commit e8f7b6f
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 1 deletion.
3 changes: 2 additions & 1 deletion aiohttp/web_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ def close(self, *, code=1000, message=b''):
if self._closing:
return True

while True:
begin = self._loop.time()
while self._loop.time() - begin < self._timeout:
try:
with Timeout(timeout=self._timeout,
loop=self._loop):
Expand Down
43 changes: 43 additions & 0 deletions tests/test_web_websocket_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,46 @@ def handler(request):
yield from ws.close()

yield from closed


@pytest.mark.run_loop
def test_close_timeout(create_app_and_client, loop):
closed = helpers.create_future(loop)

@asyncio.coroutine
def handler(request):
ws = web.WebSocketResponse(timeout=0.1)
yield from ws.prepare(request)
assert 'request' == (yield from ws.receive_str())
ws.send_str('reply')
begin = ws._loop.time()
yield from ws.close()
elapsed = ws._loop.time() - begin
assert elapsed < 0.201, \
'close() should have returned before ' \
'at most 2x timeout.'
closed.set_result(1)
return ws

app, client = yield from create_app_and_client()
app.router.add_route('GET', '/', handler)

ws = yield from client.ws_connect('/')
ws.send_str('request')
assert 'reply' == (yield from ws.receive_str())

# The server closes here. Then the client sends bogus messages with an
# internval shorter than server-side close timeout, to make the server
# hanging indefinitely.
yield from asyncio.sleep(0.04, loop=loop)
ws.send_str('hang')
yield from asyncio.sleep(0.04, loop=loop)
ws.send_str('hang')
yield from asyncio.sleep(0.04, loop=loop)
ws.send_str('hang')
yield from asyncio.sleep(0.04, loop=loop)
ws.send_str('hang')
yield from asyncio.sleep(0.04, loop=loop)
# The server should have been closed now.
assert 1 == (yield from closed)
yield from ws.close()

0 comments on commit e8f7b6f

Please sign in to comment.