Add ordering config

This commit is contained in:
Adam Hopkins 2022-12-11 10:59:30 +02:00
parent ae1669cd8f
commit 7f682cea02
No known key found for this signature in database
GPG Key ID: 9F85EE6C807303FB
7 changed files with 80 additions and 38 deletions

View File

@ -3,6 +3,7 @@ import os
import signal
import sys
from enum import Enum
from typing import Awaitable
from multidict import CIMultiDict # type: ignore
@ -19,6 +20,31 @@ except ImportError:
pass
# Python 3.11 changed the way Enum formatting works for mixed-in types.
if sys.version_info < (3, 11, 0):
class StrEnum(str, Enum):
pass
else:
from enum import StrEnum # type: ignore # noqa
class UpperStrEnum(StrEnum):
def _generate_next_value_(name, start, count, last_values):
return name.upper()
def __eq__(self, value: object) -> bool:
value = str(value).upper()
return super().__eq__(value)
def __hash__(self) -> int:
return hash(self.value)
def __str__(self) -> str:
return self.value
def enable_windows_color_support():
import ctypes

View File

@ -8,7 +8,7 @@ from pathlib import Path
from typing import Any, Callable, Dict, Optional, Sequence, Union
from warnings import filterwarnings
from sanic.constants import LocalCertCreator
from sanic.constants import LocalCertCreator, RestartOrder
from sanic.errorpages import DEFAULT_FORMAT, check_error_format
from sanic.helpers import Default, _default
from sanic.http import Http
@ -63,6 +63,7 @@ DEFAULT_CONFIG = {
"REQUEST_MAX_SIZE": 100000000, # 100 megabytes
"REQUEST_TIMEOUT": 60, # 60 seconds
"RESPONSE_TIMEOUT": 60, # 60 seconds
"RESTART_ORDER": RestartOrder.SHUTDOWN_FIRST,
"TLS_CERT_PASSWORD": "",
"TOUCHUP": _default,
"USE_UVLOOP": _default,
@ -110,6 +111,7 @@ class Config(dict, metaclass=DescriptorMeta):
REQUEST_MAX_SIZE: int
REQUEST_TIMEOUT: int
RESPONSE_TIMEOUT: int
RESTART_ORDER: Union[str, RestartOrder]
SERVER_NAME: str
TLS_CERT_PASSWORD: str
TOUCHUP: Union[Default, bool]
@ -194,6 +196,10 @@ class Config(dict, metaclass=DescriptorMeta):
self.LOCAL_CERT_CREATOR = LocalCertCreator[
self.LOCAL_CERT_CREATOR.upper()
]
elif attr == "RESTART_ORDER" and not isinstance(
self.RESTART_ORDER, RestartOrder
):
self.RESTART_ORDER = RestartOrder[self.RESTART_ORDER.upper()]
elif attr == "DEPRECATION_FILTER":
self._configure_warnings()

View File

@ -1,19 +1,9 @@
from enum import Enum, auto
from enum import auto
from sanic.compat import UpperStrEnum
class HTTPMethod(str, Enum):
def _generate_next_value_(name, start, count, last_values):
return name.upper()
def __eq__(self, value: object) -> bool:
value = str(value).upper()
return super().__eq__(value)
def __hash__(self) -> int:
return hash(self.value)
def __str__(self) -> str:
return self.value
class HTTPMethod(UpperStrEnum):
GET = auto()
POST = auto()
@ -24,15 +14,19 @@ class HTTPMethod(str, Enum):
DELETE = auto()
class LocalCertCreator(str, Enum):
def _generate_next_value_(name, start, count, last_values):
return name.upper()
class LocalCertCreator(UpperStrEnum):
AUTO = auto()
TRUSTME = auto()
MKCERT = auto()
class RestartOrder(UpperStrEnum):
SHUTDOWN_FIRST = auto()
STARTUP_FIRST = auto()
HTTP_METHODS = tuple(HTTPMethod.__members__.values())
SAFE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD, HTTPMethod.OPTIONS)
IDEMPOTENT_HTTP_METHODS = (

View File

@ -1,22 +1,10 @@
import logging
import sys
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict
from typing import Any, Dict
from warnings import warn
from sanic.compat import is_atty
# Python 3.11 changed the way Enum formatting works for mixed-in types.
if sys.version_info < (3, 11, 0):
class StrEnum(str, Enum):
pass
else:
if not TYPE_CHECKING:
from enum import StrEnum
from sanic.compat import StrEnum, is_atty
LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov

View File

@ -814,6 +814,7 @@ class StartupMixin(metaclass=SanicMeta):
cls._get_context(),
(monitor_pub, monitor_sub),
worker_state,
primary.config.RESTART_ORDER,
)
if cls.should_auto_reload():
reload_dirs: Set[Path] = primary.state.reload_dirs.union(

View File

@ -5,6 +5,7 @@ from signal import signal as signal_func
from typing import List, Optional
from sanic.compat import OS_IS_WINDOWS
from sanic.constants import RestartOrder
from sanic.exceptions import ServerKilled
from sanic.log import error_logger, logger
from sanic.worker.process import ProcessState, Worker, WorkerProcess
@ -28,6 +29,7 @@ class WorkerManager:
context,
monitor_pubsub,
worker_state,
restart_order: RestartOrder,
):
self.num_server = number
self.context = context
@ -37,6 +39,7 @@ class WorkerManager:
self.worker_state = worker_state
self.worker_state[self.MAIN_IDENT] = {"pid": self.pid}
self.terminated = False
self.restart_order = restart_order
if number == 0:
raise RuntimeError("Cannot serve with no workers")
@ -55,7 +58,14 @@ class WorkerManager:
def manage(self, ident, func, kwargs, transient=False):
container = self.transient if transient else self.durable
container.append(
Worker(ident, func, kwargs, self.context, self.worker_state)
Worker(
ident,
func,
kwargs,
self.context,
self.worker_state,
self.restart_order,
)
)
def run(self):

View File

@ -7,6 +7,7 @@ from signal import SIGINT
from threading import Thread
from typing import Any, Dict, Set
from sanic.constants import RestartOrder
from sanic.log import Colors, logger
@ -28,13 +29,22 @@ class ProcessState(IntEnum):
class WorkerProcess:
SERVER_LABEL = "Server"
def __init__(self, factory, name, target, kwargs, worker_state):
def __init__(
self,
factory,
name,
target,
kwargs,
worker_state,
restart_order: RestartOrder,
):
self.state = ProcessState.IDLE
self.factory = factory
self.name = name
self.target = target
self.kwargs = kwargs
self.worker_state = worker_state
self.restart_order = restart_order
if self.name not in self.worker_state:
self.worker_state[self.name] = {
"server": self.SERVER_LABEL in self.name
@ -96,8 +106,11 @@ class WorkerProcess:
self.name,
self.pid,
)
self._old_process = self._current_process
self.set_state(ProcessState.RESTARTING, force=True)
if self.restart_order is RestartOrder.SHUTDOWN_FIRST:
self._current_process.terminate()
else:
self._old_process = self._current_process
self.kwargs.update(
{"config": {k.upper(): v for k, v in kwargs.items()}}
)
@ -107,6 +120,7 @@ class WorkerProcess:
except AttributeError:
raise RuntimeError("Restart failed")
if self.restart_order is RestartOrder.STARTUP_FIRST:
termination_thread = Thread(target=self.wait_to_terminate)
termination_thread.start()
@ -118,7 +132,7 @@ class WorkerProcess:
}
def wait_to_terminate(self):
# TODO: Add a timeout
# TODO: Add a timeout?
while self.state is not ProcessState.ACKED:
...
else:
@ -163,6 +177,7 @@ class Worker:
server_settings,
context: BaseContext,
worker_state: Dict[str, Any],
restart_order: RestartOrder,
):
self.ident = f"{self.WORKER_PREFIX}{ident}"
self.context = context
@ -170,6 +185,7 @@ class Worker:
self.server_settings = server_settings
self.worker_state = worker_state
self.processes: Set[WorkerProcess] = set()
self.restart_order = restart_order
self.create_process()
def create_process(self) -> WorkerProcess:
@ -179,6 +195,7 @@ class Worker:
target=self.serve,
kwargs={**self.server_settings},
worker_state=self.worker_state,
restart_order=self.restart_order,
)
self.processes.add(process)
return process