Skip to content

Commit

Permalink
Sanic Server WorkerManager refactor (#2499)
Browse files Browse the repository at this point in the history
Co-authored-by: Néstor Pérez <[email protected]>
  • Loading branch information
ahopkins and prryplatypus authored Sep 18, 2022
1 parent d352a41 commit 4726cf1
Show file tree
Hide file tree
Showing 73 changed files with 3,943 additions and 954 deletions.
10 changes: 1 addition & 9 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ source = sanic
omit =
site-packages
sanic/__main__.py
sanic/server/legacy.py
sanic/compat.py
sanic/reloader_helpers.py
sanic/simple.py
sanic/utils.py
sanic/cli
Expand All @@ -21,12 +21,4 @@ exclude_lines =
NOQA
pragma: no cover
TYPE_CHECKING
omit =
site-packages
sanic/__main__.py
sanic/compat.py
sanic/reloader_helpers.py
sanic/simple.py
sanic/utils.py
sanic/cli
skip_empty = True
1 change: 0 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ codecov:
ignore:
- "sanic/__main__.py"
- "sanic/compat.py"
- "sanic/reloader_helpers.py"
- "sanic/simple.py"
- "sanic/utils.py"
- "sanic/cli"
Expand Down
14 changes: 1 addition & 13 deletions examples/unix_socket.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import os
import socket

from sanic import Sanic, response


Expand All @@ -13,13 +10,4 @@ async def test(request):


if __name__ == "__main__":
server_address = "./uds_socket"
# Make sure the socket does not already exist
try:
os.unlink(server_address)
except OSError:
if os.path.exists(server_address):
raise
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(server_address)
app.run(sock=sock)
app.run(unix="./uds_socket")
13 changes: 12 additions & 1 deletion sanic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
from sanic.blueprints import Blueprint
from sanic.constants import HTTPMethod
from sanic.request import Request
from sanic.response import HTTPResponse, html, json, text
from sanic.response import (
HTTPResponse,
empty,
file,
html,
json,
redirect,
text,
)
from sanic.server.websockets.impl import WebsocketImplProtocol as Websocket


Expand All @@ -15,7 +23,10 @@
"HTTPResponse",
"Request",
"Websocket",
"empty",
"file",
"html",
"json",
"redirect",
"text",
)
4 changes: 2 additions & 2 deletions sanic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
enable_windows_color_support()


def main():
def main(args=None):
cli = SanicCLI()
cli.attach()
cli.run()
cli.run(args)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion sanic/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "22.6.1"
__version__ = "22.9.1"
79 changes: 75 additions & 4 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from contextlib import suppress
from functools import partial
from inspect import isawaitable
from os import environ
from socket import socket
from traceback import format_exc
from types import SimpleNamespace
Expand Down Expand Up @@ -70,7 +71,7 @@
logger,
)
from sanic.mixins.listeners import ListenerEvent
from sanic.mixins.runner import RunnerMixin
from sanic.mixins.startup import StartupMixin
from sanic.models.futures import (
FutureException,
FutureListener,
Expand All @@ -88,6 +89,9 @@
from sanic.server.websockets.impl import ConnectionClosed
from sanic.signals import Signal, SignalRouter
from sanic.touchup import TouchUp, TouchUpMeta
from sanic.types.shared_ctx import SharedContext
from sanic.worker.inspector import Inspector
from sanic.worker.manager import WorkerManager


if TYPE_CHECKING:
Expand All @@ -104,7 +108,7 @@
filterwarnings("once", category=DeprecationWarning)


class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
class Sanic(BaseSanic, StartupMixin, metaclass=TouchUpMeta):
"""
The main application instance
"""
Expand All @@ -128,6 +132,8 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
"_future_routes",
"_future_signals",
"_future_statics",
"_inspector",
"_manager",
"_state",
"_task_registry",
"_test_client",
Expand All @@ -139,12 +145,14 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
"error_handler",
"go_fast",
"listeners",
"multiplexer",
"named_request_middleware",
"named_response_middleware",
"request_class",
"request_middleware",
"response_middleware",
"router",
"shared_ctx",
"signal_router",
"sock",
"strict_slashes",
Expand All @@ -171,9 +179,9 @@ def __init__(
configure_logging: bool = True,
dumps: Optional[Callable[..., AnyStr]] = None,
loads: Optional[Callable[..., Any]] = None,
inspector: bool = False,
) -> None:
super().__init__(name=name)

# logging
if configure_logging:
dict_config = log_config or LOGGING_CONFIG_DEFAULTS
Expand All @@ -187,12 +195,16 @@ def __init__(

# First setup config
self.config: Config = config or Config(env_prefix=env_prefix)
if inspector:
self.config.INSPECTOR = inspector

# Then we can do the rest
self._asgi_client: Any = None
self._blueprint_order: List[Blueprint] = []
self._delayed_tasks: List[str] = []
self._future_registry: FutureRegistry = FutureRegistry()
self._inspector: Optional[Inspector] = None
self._manager: Optional[WorkerManager] = None
self._state: ApplicationState = ApplicationState(app=self)
self._task_registry: Dict[str, Task] = {}
self._test_client: Any = None
Expand All @@ -210,6 +222,7 @@ def __init__(
self.request_middleware: Deque[MiddlewareType] = deque()
self.response_middleware: Deque[MiddlewareType] = deque()
self.router: Router = router or Router()
self.shared_ctx: SharedContext = SharedContext()
self.signal_router: SignalRouter = signal_router or SignalRouter()
self.sock: Optional[socket] = None
self.strict_slashes: bool = strict_slashes
Expand Down Expand Up @@ -243,7 +256,7 @@ def loop(self):
)
try:
return get_running_loop()
except RuntimeError:
except RuntimeError: # no cov
if sys.version_info > (3, 10):
return asyncio.get_event_loop_policy().get_event_loop()
else:
Expand Down Expand Up @@ -1353,6 +1366,7 @@ def auto_reload(self):
@auto_reload.setter
def auto_reload(self, value: bool):
self.config.AUTO_RELOAD = value
self.state.auto_reload = value

@property
def state(self) -> ApplicationState: # type: ignore
Expand Down Expand Up @@ -1470,6 +1484,18 @@ def register_app(cls, app: "Sanic") -> None:

cls._app_registry[name] = app

@classmethod
def unregister_app(cls, app: "Sanic") -> None:
"""
Unregister a Sanic instance
"""
if not isinstance(app, cls):
raise SanicException("Registered app must be an instance of Sanic")

name = app.name
if name in cls._app_registry:
del cls._app_registry[name]

@classmethod
def get_app(
cls, name: Optional[str] = None, *, force_create: bool = False
Expand All @@ -1489,6 +1515,8 @@ def get_app(
try:
return cls._app_registry[name]
except KeyError:
if name == "__main__":
return cls.get_app("__mp_main__", force_create=force_create)
if force_create:
return cls(name)
raise SanicException(f'Sanic app name "{name}" not found.')
Expand Down Expand Up @@ -1562,6 +1590,9 @@ async def _startup(self):

self.state.is_started = True

if hasattr(self, "multiplexer"):
self.multiplexer.ack()

async def _server_event(
self,
concern: str,
Expand Down Expand Up @@ -1590,3 +1621,43 @@ async def _server_event(
"loop": loop,
},
)

# -------------------------------------------------------------------- #
# Process Management
# -------------------------------------------------------------------- #

def refresh(
self,
passthru: Optional[Dict[str, Any]] = None,
):
registered = self.__class__.get_app(self.name)
if self is not registered:
if not registered.state.server_info:
registered.state.server_info = self.state.server_info
self = registered
if passthru:
for attr, info in passthru.items():
if isinstance(info, dict):
for key, value in info.items():
setattr(getattr(self, attr), key, value)
else:
setattr(self, attr, info)
if hasattr(self, "multiplexer"):
self.shared_ctx.lock()
return self

@property
def inspector(self):
if environ.get("SANIC_WORKER_PROCESS") or not self._inspector:
raise SanicException(
"Can only access the inspector from the main process"
)
return self._inspector

@property
def manager(self):
if environ.get("SANIC_WORKER_PROCESS") or not self._manager:
raise SanicException(
"Can only access the manager from the main process"
)
return self._manager
13 changes: 11 additions & 2 deletions sanic/application/constants.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
from enum import Enum, IntEnum, auto


class StrEnum(str, Enum):
class StrEnum(str, Enum): # no cov
def _generate_next_value_(name: str, *args) -> str: # type: ignore
return name.lower()

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 Server(StrEnum):
SANIC = auto()
ASGI = auto()
GUNICORN = auto()


class Mode(StrEnum):
Expand Down
2 changes: 1 addition & 1 deletion sanic/application/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def setup_ext(app: Sanic, *, fail: bool = False, **kwargs):
with suppress(ModuleNotFoundError):
sanic_ext = import_module("sanic_ext")

if not sanic_ext:
if not sanic_ext: # no cov
if fail:
raise RuntimeError(
"Sanic Extensions is not installed. You can add it to your "
Expand Down
19 changes: 11 additions & 8 deletions sanic/application/motd.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,23 @@ def set_variables(self): # no cov
)
self.display_length = self.key_width + self.value_width + 2

def display(self):
version = f"Sanic v{__version__}".center(self.centering_length)
def display(self, version=True, action="Goin' Fast", out=None):
if not out:
out = logger.info
header = "Sanic"
if version:
header += f" v{__version__}"
header = header.center(self.centering_length)
running = (
f"Goin' Fast @ {self.serve_location}"
if self.serve_location
else ""
f"{action} @ {self.serve_location}" if self.serve_location else ""
).center(self.centering_length)
length = len(version) + 2 - self.logo_line_length
length = len(header) + 2 - self.logo_line_length
first_filler = "─" * (self.logo_line_length - 1)
second_filler = "─" * length
display_filler = "─" * (self.display_length + 2)
lines = [
f"\n{first_filler}{second_filler}┐",
f"│ {version} │",
f"│ {header} │",
f"│ {running} │",
f"├{first_filler}{second_filler}┤",
]
Expand All @@ -107,7 +110,7 @@ def display(self):
self._render_fill(lines)

lines.append(f"└{first_filler}{second_filler}\n")
logger.info(indent("\n".join(lines), " "))
out(indent("\n".join(lines), " "))

def _render_data(self, lines, data, start):
offset = 0
Expand Down
Loading

0 comments on commit 4726cf1

Please sign in to comment.