Skip to content

Fix #180: Server unable to shutdown while persistent connections exist #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 60 additions & 10 deletions tests/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import asyncio
import unittest
import weakref

from uvloop import _testbase as tb

Expand All @@ -25,12 +26,11 @@ async def on_request(request):
app = aiohttp.web.Application()
app.router.add_get('/', on_request)

f = self.loop.create_server(
app.make_handler(),
'0.0.0.0', '0')
srv = self.loop.run_until_complete(f)

port = srv.sockets[0].getsockname()[1]
runner = aiohttp.web.AppRunner(app)
self.loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0')
self.loop.run_until_complete(site.start())
port = site._server.sockets[0].getsockname()[1]

async def test():
# Make sure we're using the correct event loop.
Expand All @@ -45,11 +45,61 @@ async def test():
self.assertEqual(result, PAYLOAD)

self.loop.run_until_complete(test())
self.loop.run_until_complete(app.shutdown())
self.loop.run_until_complete(app.cleanup())
self.loop.run_until_complete(runner.cleanup())

def test_aiohttp_graceful_shutdown(self):
async def websocket_handler(request):
ws = aiohttp.web.WebSocketResponse()
await ws.prepare(request)
request.app['websockets'].add(ws)
try:
async for msg in ws:
await ws.send_str(msg.data)
finally:
request.app['websockets'].discard(ws)
return ws

async def on_shutdown(app):
for ws in set(app['websockets']):
await ws.close(
code=aiohttp.WSCloseCode.GOING_AWAY,
message='Server shutdown')

asyncio.set_event_loop(self.loop)
app = aiohttp.web.Application()
app.router.add_get('/', websocket_handler)
app.on_shutdown.append(on_shutdown)
app['websockets'] = weakref.WeakSet()

runner = aiohttp.web.AppRunner(app)
self.loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0')
self.loop.run_until_complete(site.start())
port = site._server.sockets[0].getsockname()[1]

async def client():
async with aiohttp.ClientSession() as client:
async with client.ws_connect(
'http://127.0.0.1:{}'.format(port)) as ws:
await ws.send_str("hello")
async for msg in ws:
assert msg.data == "hello"

client_task = asyncio.ensure_future(client())

async def stop():
await asyncio.sleep(0.1)
try:
await asyncio.wait_for(runner.cleanup(), timeout=0.1)
finally:
try:
client_task.cancel()
await client_task
except asyncio.CancelledError:
pass

self.loop.run_until_complete(stop())

srv.close()
self.loop.run_until_complete(srv.wait_closed())


@unittest.skipIf(skip_tests, "no aiohttp module")
Expand Down
5 changes: 4 additions & 1 deletion uvloop/server.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ cdef class Server:

@cython.iterable_coroutine
async def wait_closed(self):
if self._waiters is None:
# Do not remove `self._servers is None` below
# because close() method only closes server sockets
# and existing client connections are left open.
if self._servers is None or self._waiters is None:
return
waiter = self._loop._new_future()
self._waiters.append(waiter)
Expand Down