Fixes "after_server_start" when using return_asyncio_server. (#1676)
* Fixes ability to trigger "after_server_start", "before_server_stop", "after_server_stop" server events when using app.create_server to start your own asyncio_server See example file run_async_advanced for a full example * Fix a missing method on AsyncServer that some tests need Add a tiny bit more documentation in-code Change name of AsyncServerCoro to AsyncioServer
This commit is contained in:
parent
e13f42c17b
commit
7674e917e4
|
@ -157,4 +157,35 @@ server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True
|
|||
loop = asyncio.get_event_loop()
|
||||
task = asyncio.ensure_future(server)
|
||||
loop.run_forever()
|
||||
```
|
||||
|
||||
Caveat: using this method, calling `app.create_server()` will trigger "before_server_start" server events, but not
|
||||
"after_server_start", "before_server_stop", or "after_server_stop" server events.
|
||||
|
||||
For more advanced use-cases, you can trigger these events using the AsyncioServer object, returned by awaiting
|
||||
the server task.
|
||||
|
||||
Here is an incomplete example (please see `run_async_advanced.py` in examples for something more complete):
|
||||
|
||||
```python
|
||||
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||
server = loop.run_until_complete(serv_task)
|
||||
server.after_start()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
loop.stop()
|
||||
finally:
|
||||
server.before_stop()
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
||||
```
|
38
examples/run_async_advanced.py
Normal file
38
examples/run_async_advanced.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from sanic import Sanic
|
||||
from sanic import response
|
||||
from signal import signal, SIGINT
|
||||
import asyncio
|
||||
import uvloop
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
@app.listener('after_server_start')
|
||||
async def after_start_test(app, loop):
|
||||
print("Async Server Started!")
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return response.json({"answer": "42"})
|
||||
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||
signal(SIGINT, lambda s, f: loop.stop())
|
||||
server = loop.run_until_complete(serv_task)
|
||||
server.after_start()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
loop.stop()
|
||||
finally:
|
||||
server.before_stop()
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
|
@ -634,6 +634,78 @@ def trigger_events(events, loop):
|
|||
loop.run_until_complete(result)
|
||||
|
||||
|
||||
class AsyncioServer:
|
||||
"""
|
||||
Wraps an asyncio server with functionality that might be useful to
|
||||
a user who needs to manage the server lifecycle manually.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"loop",
|
||||
"serve_coro",
|
||||
"_after_start",
|
||||
"_before_stop",
|
||||
"_after_stop",
|
||||
"server",
|
||||
"connections",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
loop,
|
||||
serve_coro,
|
||||
connections,
|
||||
after_start,
|
||||
before_stop,
|
||||
after_stop,
|
||||
):
|
||||
# Note, Sanic already called "before_server_start" events
|
||||
# before this helper was even created. So we don't need it here.
|
||||
self.loop = loop
|
||||
self.serve_coro = serve_coro
|
||||
self._after_start = after_start
|
||||
self._before_stop = before_stop
|
||||
self._after_stop = after_stop
|
||||
self.server = None
|
||||
self.connections = connections
|
||||
|
||||
def after_start(self):
|
||||
"""Trigger "after_server_start" events"""
|
||||
trigger_events(self._after_start, self.loop)
|
||||
|
||||
def before_stop(self):
|
||||
"""Trigger "before_server_stop" events"""
|
||||
trigger_events(self._before_stop, self.loop)
|
||||
|
||||
def after_stop(self):
|
||||
"""Trigger "after_server_stop" events"""
|
||||
trigger_events(self._after_stop, self.loop)
|
||||
|
||||
def is_serving(self):
|
||||
if self.server:
|
||||
return self.server.is_serving()
|
||||
return False
|
||||
|
||||
def wait_closed(self):
|
||||
if self.server:
|
||||
return self.server.wait_closed()
|
||||
|
||||
def close(self):
|
||||
if self.server:
|
||||
self.server.close()
|
||||
coro = self.wait_closed()
|
||||
task = asyncio.ensure_future(coro, loop=self.loop)
|
||||
return task
|
||||
|
||||
def __await__(self):
|
||||
"""Starts the asyncio server, returns AsyncServerCoro"""
|
||||
task = asyncio.ensure_future(self.serve_coro)
|
||||
while not task.done():
|
||||
yield
|
||||
self.server = task.result()
|
||||
return self
|
||||
|
||||
|
||||
def serve(
|
||||
host,
|
||||
port,
|
||||
|
@ -700,6 +772,8 @@ def serve(
|
|||
:param reuse_port: `True` for multiple workers
|
||||
:param loop: asyncio compatible event loop
|
||||
:param protocol: subclass of asyncio protocol class
|
||||
:param run_async: bool: Do not create a new event loop for the server,
|
||||
and return an AsyncServer object rather than running it
|
||||
:param request_class: Request class to use
|
||||
:param access_log: disable/enable access log
|
||||
:param websocket_max_size: enforces the maximum size for
|
||||
|
@ -771,7 +845,14 @@ def serve(
|
|||
)
|
||||
|
||||
if run_async:
|
||||
return server_coroutine
|
||||
return AsyncioServer(
|
||||
loop,
|
||||
server_coroutine,
|
||||
connections,
|
||||
after_start,
|
||||
before_stop,
|
||||
after_stop,
|
||||
)
|
||||
|
||||
trigger_events(before_start, loop)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import signal
|
||||
|
||||
import pytest
|
||||
|
@ -89,3 +90,52 @@ async def test_trigger_before_events_create_server(app):
|
|||
|
||||
assert hasattr(app, "db")
|
||||
assert isinstance(app.db, MySanicDb)
|
||||
|
||||
def test_create_server_trigger_events(app):
|
||||
"""Test if create_server can trigger server events"""
|
||||
|
||||
flag1 = False
|
||||
flag2 = False
|
||||
flag3 = False
|
||||
|
||||
async def stop(app, loop):
|
||||
nonlocal flag1
|
||||
flag1 = True
|
||||
await asyncio.sleep(0.1)
|
||||
app.stop()
|
||||
|
||||
async def before_stop(app, loop):
|
||||
nonlocal flag2
|
||||
flag2 = True
|
||||
|
||||
async def after_stop(app, loop):
|
||||
nonlocal flag3
|
||||
flag3 = True
|
||||
|
||||
app.listener("after_server_start")(stop)
|
||||
app.listener("before_server_stop")(before_stop)
|
||||
app.listener("after_server_stop")(after_stop)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
serv_coro = app.create_server(return_asyncio_server=True)
|
||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||
server = loop.run_until_complete(serv_task)
|
||||
server.after_start()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
loop.stop()
|
||||
finally:
|
||||
# Run the on_stop function if provided
|
||||
server.before_stop()
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
|
||||
# Complete all tasks on the loop
|
||||
signal.stopped = True
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
||||
assert flag1 and flag2 and flag3
|
||||
|
|
Loading…
Reference in New Issue
Block a user