This commit is contained in:
Adam Hopkins 2022-12-12 11:26:29 +02:00
parent 4e4e2b036b
commit 0e7ee94574
No known key found for this signature in database
GPG Key ID: 9F85EE6C807303FB
2 changed files with 85 additions and 5 deletions

View File

@ -108,7 +108,7 @@ class WorkerProcess:
) )
self.set_state(ProcessState.RESTARTING, force=True) self.set_state(ProcessState.RESTARTING, force=True)
if self.restart_order is RestartOrder.SHUTDOWN_FIRST: if self.restart_order is RestartOrder.SHUTDOWN_FIRST:
self._current_process.terminate() self._terminate_now()
else: else:
self._old_process = self._current_process self._old_process = self._current_process
self.kwargs.update( self.kwargs.update(
@ -121,8 +121,7 @@ class WorkerProcess:
raise RuntimeError("Restart failed") raise RuntimeError("Restart failed")
if self.restart_order is RestartOrder.STARTUP_FIRST: if self.restart_order is RestartOrder.STARTUP_FIRST:
termination_thread = Thread(target=self.wait_to_terminate) self._terminate_soon()
termination_thread.start()
self.worker_state[self.name] = { self.worker_state[self.name] = {
**self.worker_state[self.name], **self.worker_state[self.name],
@ -131,7 +130,28 @@ class WorkerProcess:
"restart_at": get_now(), "restart_at": get_now(),
} }
def wait_to_terminate(self): def _terminate_now(self):
logger.debug(
f"{Colors.BLUE}Begin restart termination: "
f"{Colors.BOLD}{Colors.SANIC}"
f"%s {Colors.BLUE}[%s]{Colors.END}",
self.name,
self._current_process.pid,
)
self._current_process.terminate()
def _terminate_soon(self):
logger.debug(
f"{Colors.BLUE}Begin restart termination: "
f"{Colors.BOLD}{Colors.SANIC}"
f"%s {Colors.BLUE}[%s]{Colors.END}",
self.name,
self._current_process.pid,
)
termination_thread = Thread(target=self._wait_to_terminate)
termination_thread.start()
def _wait_to_terminate(self):
# TODO: Add a timeout? # TODO: Add a timeout?
while self.state is not ProcessState.ACKED: while self.state is not ProcessState.ACKED:
... ...

View File

@ -1,13 +1,18 @@
import re
import signal import signal
import threading
from asyncio import Event from asyncio import Event
from logging import DEBUG
from pathlib import Path from pathlib import Path
from unittest.mock import Mock from unittest.mock import Mock, patch
import pytest import pytest
from sanic.app import Sanic from sanic.app import Sanic
from sanic.constants import RestartOrder
from sanic.worker.loader import AppLoader from sanic.worker.loader import AppLoader
from sanic.worker.process import ProcessState, WorkerProcess
from sanic.worker.reloader import Reloader from sanic.worker.reloader import Reloader
@ -154,3 +159,58 @@ def test_check_file(tmp_path):
assert Reloader.check_file(current, mtimes) is False assert Reloader.check_file(current, mtimes) is False
mtimes[current] = mtimes[current] - 1 mtimes[current] = mtimes[current] - 1
assert Reloader.check_file(current, mtimes) is True assert Reloader.check_file(current, mtimes) is True
@pytest.mark.parametrize(
"order,expected",
(
(
"shutdown_first",
[
"Restarting a process",
"Begin restart termination",
"Starting a process",
],
),
(
"startup_first",
[
"Restarting a process",
"Starting a process",
"Begin restart termination",
"Process acked. Terminating",
],
),
),
)
def test_default_reload_shutdown_order(monkeypatch, caplog, order, expected):
current_process = Mock()
worker_process = WorkerProcess(
lambda **_: current_process,
"Test",
lambda **_: ...,
{},
{},
RestartOrder[order.upper()],
)
def start(self):
worker_process.set_state(ProcessState.ACKED)
self._target()
monkeypatch.setattr(threading.Thread, "start", start)
with caplog.at_level(DEBUG):
worker_process.restart()
ansi = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
def clean(msg: str):
msg, _ = ansi.sub("", msg).split(":", 1)
return msg
debug = [clean(record[2]) for record in caplog.record_tuples]
assert debug == expected
current_process.start.assert_called_once()
current_process.terminate.assert_called_once()