Add two new events on the reloader process (#2413)
This commit is contained in:
parent
0c9df02e66
commit
2a8e91052f
|
@ -17,6 +17,8 @@ class ListenerEvent(str, Enum):
|
||||||
AFTER_SERVER_STOP = "server.shutdown.after"
|
AFTER_SERVER_STOP = "server.shutdown.after"
|
||||||
MAIN_PROCESS_START = auto()
|
MAIN_PROCESS_START = auto()
|
||||||
MAIN_PROCESS_STOP = auto()
|
MAIN_PROCESS_STOP = auto()
|
||||||
|
RELOAD_PROCESS_START = auto()
|
||||||
|
RELOAD_PROCESS_STOP = auto()
|
||||||
|
|
||||||
|
|
||||||
class ListenerMixin(metaclass=SanicMeta):
|
class ListenerMixin(metaclass=SanicMeta):
|
||||||
|
@ -73,6 +75,16 @@ class ListenerMixin(metaclass=SanicMeta):
|
||||||
) -> ListenerType[Sanic]:
|
) -> ListenerType[Sanic]:
|
||||||
return self.listener(listener, "main_process_stop")
|
return self.listener(listener, "main_process_stop")
|
||||||
|
|
||||||
|
def reload_process_start(
|
||||||
|
self, listener: ListenerType[Sanic]
|
||||||
|
) -> ListenerType[Sanic]:
|
||||||
|
return self.listener(listener, "reload_process_start")
|
||||||
|
|
||||||
|
def reload_process_stop(
|
||||||
|
self, listener: ListenerType[Sanic]
|
||||||
|
) -> ListenerType[Sanic]:
|
||||||
|
return self.listener(listener, "reload_process_stop")
|
||||||
|
|
||||||
def before_server_start(
|
def before_server_start(
|
||||||
self, listener: ListenerType[Sanic]
|
self, listener: ListenerType[Sanic]
|
||||||
) -> ListenerType[Sanic]:
|
) -> ListenerType[Sanic]:
|
||||||
|
|
|
@ -11,6 +11,7 @@ from asyncio import (
|
||||||
all_tasks,
|
all_tasks,
|
||||||
get_event_loop,
|
get_event_loop,
|
||||||
get_running_loop,
|
get_running_loop,
|
||||||
|
new_event_loop,
|
||||||
)
|
)
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
@ -32,6 +33,7 @@ from sanic.models.handler_types import ListenerType
|
||||||
from sanic.server import Signal as ServerSignal
|
from sanic.server import Signal as ServerSignal
|
||||||
from sanic.server import try_use_uvloop
|
from sanic.server import try_use_uvloop
|
||||||
from sanic.server.async_server import AsyncioServer
|
from sanic.server.async_server import AsyncioServer
|
||||||
|
from sanic.server.events import trigger_events
|
||||||
from sanic.server.protocols.http_protocol import HttpProtocol
|
from sanic.server.protocols.http_protocol import HttpProtocol
|
||||||
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
||||||
from sanic.server.runners import serve, serve_multiple, serve_single
|
from sanic.server.runners import serve, serve_multiple, serve_single
|
||||||
|
@ -538,15 +540,21 @@ class RunnerMixin(metaclass=SanicMeta):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError("Did not find any applications.")
|
raise RuntimeError("Did not find any applications.")
|
||||||
|
|
||||||
|
reloader_start = primary.listeners.get("reload_process_start")
|
||||||
|
reloader_stop = primary.listeners.get("reload_process_stop")
|
||||||
# We want to run auto_reload if ANY of the applications have it enabled
|
# We want to run auto_reload if ANY of the applications have it enabled
|
||||||
if (
|
if (
|
||||||
cls.should_auto_reload()
|
cls.should_auto_reload()
|
||||||
and os.environ.get("SANIC_SERVER_RUNNING") != "true"
|
and os.environ.get("SANIC_SERVER_RUNNING") != "true"
|
||||||
):
|
): # no cov
|
||||||
|
loop = new_event_loop()
|
||||||
|
trigger_events(reloader_start, loop)
|
||||||
reload_dirs: Set[Path] = primary.state.reload_dirs.union(
|
reload_dirs: Set[Path] = primary.state.reload_dirs.union(
|
||||||
*(app.state.reload_dirs for app in apps)
|
*(app.state.reload_dirs for app in apps)
|
||||||
)
|
)
|
||||||
return reloader_helpers.watchdog(1.0, reload_dirs)
|
reloader_helpers.watchdog(1.0, reload_dirs)
|
||||||
|
trigger_events(reloader_stop, loop)
|
||||||
|
return
|
||||||
|
|
||||||
# This exists primarily for unit testing
|
# This exists primarily for unit testing
|
||||||
if not primary.state.server_info: # no cov
|
if not primary.state.server_info: # no cov
|
||||||
|
|
|
@ -58,6 +58,36 @@ def write_app(filename, **runargs):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def write_listener_app(filename, **runargs):
|
||||||
|
start_text = secrets.token_urlsafe()
|
||||||
|
stop_text = secrets.token_urlsafe()
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(
|
||||||
|
dedent(
|
||||||
|
f"""\
|
||||||
|
import os
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
app.route("/")(lambda x: x)
|
||||||
|
|
||||||
|
@app.reload_process_start
|
||||||
|
async def reload_start(*_):
|
||||||
|
print("reload_start", os.getpid(), {start_text!r})
|
||||||
|
|
||||||
|
@app.reload_process_stop
|
||||||
|
async def reload_stop(*_):
|
||||||
|
print("reload_stop", os.getpid(), {stop_text!r})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(**{runargs!r})
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return start_text, stop_text
|
||||||
|
|
||||||
|
|
||||||
def write_json_config_app(filename, jsonfile, **runargs):
|
def write_json_config_app(filename, jsonfile, **runargs):
|
||||||
with open(filename, "w") as f:
|
with open(filename, "w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
|
@ -92,10 +122,10 @@ def write_file(filename):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def scanner(proc):
|
def scanner(proc, trigger="complete"):
|
||||||
for line in proc.stdout:
|
for line in proc.stdout:
|
||||||
line = line.decode().strip()
|
line = line.decode().strip()
|
||||||
if line.startswith("complete"):
|
if line.startswith(trigger):
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +138,7 @@ argv = dict(
|
||||||
"sanic",
|
"sanic",
|
||||||
"--port",
|
"--port",
|
||||||
"42204",
|
"42204",
|
||||||
"--debug",
|
"--auto-reload",
|
||||||
"reloader.app",
|
"reloader.app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -118,7 +148,7 @@ argv = dict(
|
||||||
"runargs, mode",
|
"runargs, mode",
|
||||||
[
|
[
|
||||||
(dict(port=42202, auto_reload=True), "script"),
|
(dict(port=42202, auto_reload=True), "script"),
|
||||||
(dict(port=42203, debug=True), "module"),
|
(dict(port=42203, auto_reload=True), "module"),
|
||||||
({}, "sanic"),
|
({}, "sanic"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -151,7 +181,7 @@ async def test_reloader_live(runargs, mode):
|
||||||
"runargs, mode",
|
"runargs, mode",
|
||||||
[
|
[
|
||||||
(dict(port=42302, auto_reload=True), "script"),
|
(dict(port=42302, auto_reload=True), "script"),
|
||||||
(dict(port=42303, debug=True), "module"),
|
(dict(port=42303, auto_reload=True), "module"),
|
||||||
({}, "sanic"),
|
({}, "sanic"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -183,3 +213,30 @@ async def test_reloader_live_with_dir(runargs, mode):
|
||||||
terminate(proc)
|
terminate(proc)
|
||||||
with suppress(TimeoutExpired):
|
with suppress(TimeoutExpired):
|
||||||
proc.wait(timeout=3)
|
proc.wait(timeout=3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_listeners():
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
filename = os.path.join(tmpdir, "reloader.py")
|
||||||
|
start_text, stop_text = write_listener_app(
|
||||||
|
filename, port=42305, auto_reload=True
|
||||||
|
)
|
||||||
|
|
||||||
|
proc = Popen(
|
||||||
|
argv["script"], cwd=tmpdir, stdout=PIPE, creationflags=flags
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
timeout = Timer(TIMER_DELAY, terminate, [proc])
|
||||||
|
timeout.start()
|
||||||
|
# Python apparently keeps using the old source sometimes if
|
||||||
|
# we don't sleep before rewrite (pycache timestamp problem?)
|
||||||
|
sleep(1)
|
||||||
|
line = scanner(proc, "reload_start")
|
||||||
|
assert start_text in next(line)
|
||||||
|
line = scanner(proc, "reload_stop")
|
||||||
|
assert stop_text in next(line)
|
||||||
|
finally:
|
||||||
|
timeout.cancel()
|
||||||
|
terminate(proc)
|
||||||
|
with suppress(TimeoutExpired):
|
||||||
|
proc.wait(timeout=3)
|
||||||
|
|
|
@ -199,3 +199,16 @@ async def test_missing_startup_raises_exception(app):
|
||||||
|
|
||||||
with pytest.raises(SanicException):
|
with pytest.raises(SanicException):
|
||||||
await srv.before_start()
|
await srv.before_start()
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_listeners_attached(app):
|
||||||
|
async def dummy(*_):
|
||||||
|
...
|
||||||
|
|
||||||
|
app.reload_process_start(dummy)
|
||||||
|
app.reload_process_stop(dummy)
|
||||||
|
app.listener("reload_process_start")(dummy)
|
||||||
|
app.listener("reload_process_stop")(dummy)
|
||||||
|
|
||||||
|
assert len(app.listeners.get("reload_process_start")) == 2
|
||||||
|
assert len(app.listeners.get("reload_process_stop")) == 2
|
||||||
|
|
Loading…
Reference in New Issue
Block a user