Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API to define a number of workers #2701

Merged
merged 4 commits into from Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 30 additions & 3 deletions sanic/worker/manager.py
Expand Up @@ -5,7 +5,7 @@
from random import choice
from signal import SIGINT, SIGTERM, Signals
from signal import signal as signal_func
from typing import Dict, List, Optional
from typing import Any, Callable, Dict, List, Optional

from sanic.compat import OS_IS_WINDOWS
from sanic.exceptions import ServerKilled
Expand Down Expand Up @@ -54,9 +54,36 @@ def __init__(
signal_func(SIGINT, self.shutdown_signal)
signal_func(SIGTERM, self.shutdown_signal)

def manage(self, ident, func, kwargs, transient=False) -> Worker:
def manage(
self,
ident: str,
func: Callable[..., Any],
kwargs: Dict[str, Any],
transient: bool = False,
workers: int = 1,
) -> Worker:
"""
Instruct Sanic to manage a custom process.

:param ident: A name for the worker process
:type ident: str
:param func: The function to call in the background process
:type func: Callable[..., Any]
:param kwargs: Arguments to pass to the function
:type kwargs: Dict[str, Any]
:param transient: Whether to mark the process as transient. If True
then the Worker Manager will restart the process along
with any global restart (ex: auto-reload), defaults to False
:type transient: bool, optional
:param workers: The number of worker processes to run, defaults to 1
:type workers: int, optional
:return: The Worker instance
:rtype: Worker
"""
container = self.transient if transient else self.durable
worker = Worker(ident, func, kwargs, self.context, self.worker_state)
worker = Worker(
ident, func, kwargs, self.context, self.worker_state, workers
)
container[worker.ident] = worker
return worker

Expand Down
5 changes: 4 additions & 1 deletion sanic/worker/process.py
Expand Up @@ -192,14 +192,17 @@ def __init__(
server_settings,
context: BaseContext,
worker_state: Dict[str, Any],
num: int = 1,
):
self.ident = ident
self.num = num
self.context = context
self.serve = serve
self.server_settings = server_settings
self.worker_state = worker_state
self.processes: Set[WorkerProcess] = set()
self.create_process()
for _ in range(num):
self.create_process()

def create_process(self) -> WorkerProcess:
process = WorkerProcess(
Expand Down
90 changes: 74 additions & 16 deletions tests/test_errorpages.py
Expand Up @@ -4,7 +4,7 @@

from sanic import Sanic
from sanic.config import Config
from sanic.errorpages import TextRenderer, guess_mime, exception_response
from sanic.errorpages import TextRenderer, exception_response, guess_mime
from sanic.exceptions import NotFound, SanicException
from sanic.handlers import ErrorHandler
from sanic.request import Request
Expand Down Expand Up @@ -57,7 +57,6 @@ def mixed_fail(request, param):
raise Exception
return json({}) if param == "json" else html("")


return app


Expand Down Expand Up @@ -314,7 +313,6 @@ def handler(_):
("*/*", "application/json", "application/json"),
# App wants text/plain but accept has equal entries for it
("text/*,*/plain", None, "text/plain; charset=utf-8"),

),
)
def test_combinations_for_auto(fake_request, accept, content_type, expected):
Expand Down Expand Up @@ -428,25 +426,83 @@ def test_config_fallback_bad_value(app):
@pytest.mark.parametrize(
"route_format,fallback,accept,expected",
(
("json", "html", "*/*", "The client accepts */*, using 'json' from fakeroute"),
("json", "auto", "text/html,*/*;q=0.8", "The client accepts text/html, using 'html' from any"),
("json", "json", "text/html,*/*;q=0.8", "The client accepts */*;q=0.8, using 'json' from fakeroute"),
("", "html", "text/*,*/plain", "The client accepts text/*, using 'html' from FALLBACK_ERROR_FORMAT"),
("", "json", "text/*,*/*", "The client accepts */*, using 'json' from FALLBACK_ERROR_FORMAT"),
("", "auto", "*/*,application/json;q=0.5", "The client accepts */*, using 'json' from request.accept"),
("", "auto", "*/*", "The client accepts */*, using 'json' from content-type"),
("", "auto", "text/html,text/plain", "The client accepts text/plain, using 'text' from any"),
("", "auto", "text/html,text/plain;q=0.9", "The client accepts text/html, using 'html' from any"),
("html", "json", "application/xml", "No format found, the client accepts [application/xml]"),
(
"json",
"html",
"*/*",
"The client accepts */*, using 'json' from fakeroute",
),
(
"json",
"auto",
"text/html,*/*;q=0.8",
"The client accepts text/html, using 'html' from any",
),
(
"json",
"json",
"text/html,*/*;q=0.8",
"The client accepts */*;q=0.8, using 'json' from fakeroute",
),
(
"",
"html",
"text/*,*/plain",
"The client accepts text/*, using 'html' from FALLBACK_ERROR_FORMAT",
),
(
"",
"json",
"text/*,*/*",
"The client accepts */*, using 'json' from FALLBACK_ERROR_FORMAT",
),
(
"",
"auto",
"*/*,application/json;q=0.5",
"The client accepts */*, using 'json' from request.accept",
),
(
"",
"auto",
"*/*",
"The client accepts */*, using 'json' from content-type",
),
(
"",
"auto",
"text/html,text/plain",
"The client accepts text/plain, using 'text' from any",
),
(
"",
"auto",
"text/html,text/plain;q=0.9",
"The client accepts text/html, using 'html' from any",
),
(
"html",
"json",
"application/xml",
"No format found, the client accepts [application/xml]",
),
("", "auto", "*/*", "The client accepts */*, using 'text' from any"),
("", "", "*/*", "No format found, the client accepts [*/*]"),
# DEPRECATED: remove in 24.3
("", "auto", "*/*", "The client accepts */*, using 'json' from request.json"),
(
"",
"auto",
"*/*",
"The client accepts */*, using 'json' from request.json",
),
),
)
def test_guess_mime_logging(caplog, fake_request, route_format, fallback, accept, expected):
def test_guess_mime_logging(
caplog, fake_request, route_format, fallback, accept, expected
):
class FakeObject:
pass

fake_request.route = FakeObject()
fake_request.route.name = "fakeroute"
fake_request.route.extra = FakeObject()
Expand All @@ -466,6 +522,8 @@ class FakeObject:
with caplog.at_level(logging.DEBUG, logger="sanic.root"):
guess_mime(fake_request, fallback)

logmsg, = [r.message for r in caplog.records if r.funcName == "guess_mime"]
(logmsg,) = [
r.message for r in caplog.records if r.funcName == "guess_mime"
]

assert logmsg == expected