Optional uvloop use (#2264)
Co-authored-by: Adam Hopkins <adam@amhopkins.com> Co-authored-by: Adam Hopkins <admhpkns@gmail.com>
This commit is contained in:
44
sanic/app.py
44
sanic/app.py
@@ -69,6 +69,7 @@ from sanic.exceptions import (
|
||||
URLBuildError,
|
||||
)
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.helpers import _default
|
||||
from sanic.http import Stage
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, error_logger, logger
|
||||
from sanic.mixins.listeners import ListenerEvent
|
||||
@@ -88,7 +89,7 @@ from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||
from sanic.router import Router
|
||||
from sanic.server import AsyncioServer, HttpProtocol
|
||||
from sanic.server import Signal as ServerSignal
|
||||
from sanic.server import serve, serve_multiple, serve_single
|
||||
from sanic.server import serve, serve_multiple, serve_single, try_use_uvloop
|
||||
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
||||
from sanic.server.websockets.impl import ConnectionClosed
|
||||
from sanic.signals import Signal, SignalRouter
|
||||
@@ -130,6 +131,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
"_task_registry",
|
||||
"_test_client",
|
||||
"_test_manager",
|
||||
"_uvloop_setting", # TODO: Remove in v22.6
|
||||
"asgi",
|
||||
"auto_reload",
|
||||
"auto_reload",
|
||||
@@ -159,6 +161,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
)
|
||||
|
||||
_app_registry: Dict[str, "Sanic"] = {}
|
||||
_uvloop_setting = None
|
||||
test_mode = False
|
||||
|
||||
def __init__(
|
||||
@@ -1142,6 +1145,11 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
register_sys_signals=register_sys_signals,
|
||||
)
|
||||
|
||||
if self.config.USE_UVLOOP is True or (
|
||||
self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS
|
||||
):
|
||||
try_use_uvloop()
|
||||
|
||||
try:
|
||||
self.is_running = True
|
||||
self.is_stopping = False
|
||||
@@ -1239,12 +1247,13 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
||||
)
|
||||
|
||||
# if access_log is passed explicitly change config.ACCESS_LOG
|
||||
if access_log is not None:
|
||||
self.config.ACCESS_LOG = access_log
|
||||
|
||||
if noisy_exceptions is not None:
|
||||
self.config.NOISY_EXCEPTIONS = noisy_exceptions
|
||||
# Set explicitly passed configuration values
|
||||
for attribute, value in {
|
||||
"ACCESS_LOG": access_log,
|
||||
"NOISY_EXCEPTIONS": noisy_exceptions,
|
||||
}.items():
|
||||
if value is not None:
|
||||
setattr(self.config, attribute, value)
|
||||
|
||||
server_settings = self._helper(
|
||||
host=host,
|
||||
@@ -1259,6 +1268,14 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
run_async=return_asyncio_server,
|
||||
)
|
||||
|
||||
if self.config.USE_UVLOOP is not _default:
|
||||
error_logger.warning(
|
||||
"You are trying to change the uvloop configuration, but "
|
||||
"this is only effective when using the run(...) method. "
|
||||
"When using the create_server(...) method Sanic will use "
|
||||
"the already existing loop."
|
||||
)
|
||||
|
||||
main_start = server_settings.pop("main_start", None)
|
||||
main_stop = server_settings.pop("main_stop", None)
|
||||
if main_start or main_stop:
|
||||
@@ -1833,6 +1850,19 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||
self._future_registry.clear()
|
||||
self.signalize()
|
||||
self.finalize()
|
||||
|
||||
# TODO: Replace in v22.6 to check against apps in app registry
|
||||
if (
|
||||
self.__class__._uvloop_setting is not None
|
||||
and self.__class__._uvloop_setting != self.config.USE_UVLOOP
|
||||
):
|
||||
error_logger.warning(
|
||||
"It looks like you're running several apps with different "
|
||||
"uvloop settings. This is not supported and may lead to "
|
||||
"unintended behaviour."
|
||||
)
|
||||
self.__class__._uvloop_setting = self.config.USE_UVLOOP
|
||||
|
||||
ErrorHandler.finalize(self.error_handler, config=self.config)
|
||||
TouchUp.run(self)
|
||||
self.state.is_started = True
|
||||
|
||||
@@ -7,6 +7,7 @@ import sanic.app # noqa
|
||||
|
||||
from sanic.compat import Header
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.helpers import _default
|
||||
from sanic.http import Stage
|
||||
from sanic.models.asgi import ASGIReceive, ASGIScope, ASGISend, MockTransport
|
||||
from sanic.request import Request
|
||||
@@ -53,6 +54,13 @@ class Lifespan:
|
||||
await self.asgi_app.sanic_app._server_event("init", "before")
|
||||
await self.asgi_app.sanic_app._server_event("init", "after")
|
||||
|
||||
if self.asgi_app.sanic_app.config.USE_UVLOOP is not _default:
|
||||
warnings.warn(
|
||||
"You have set the USE_UVLOOP configuration option, but Sanic "
|
||||
"cannot control the event loop when running in ASGI mode."
|
||||
"This option will be ignored."
|
||||
)
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""
|
||||
Gather the listeners to fire on server stop.
|
||||
|
||||
@@ -8,6 +8,14 @@ from multidict import CIMultiDict # type: ignore
|
||||
|
||||
|
||||
OS_IS_WINDOWS = os.name == "nt"
|
||||
UVLOOP_INSTALLED = False
|
||||
|
||||
try:
|
||||
import uvloop # type: ignore # noqa
|
||||
|
||||
UVLOOP_INSTALLED = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def enable_windows_color_support():
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, Optional, Sequence, Union
|
||||
from warnings import warn
|
||||
|
||||
from sanic.errorpages import DEFAULT_FORMAT, check_error_format
|
||||
from sanic.helpers import _default
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.http import Http
|
||||
from sanic.log import error_logger
|
||||
from sanic.utils import load_module_from_file_location, str_to_bool
|
||||
@@ -38,6 +38,7 @@ DEFAULT_CONFIG = {
|
||||
"REQUEST_MAX_SIZE": 100000000, # 100 megabytes
|
||||
"REQUEST_TIMEOUT": 60, # 60 seconds
|
||||
"RESPONSE_TIMEOUT": 60, # 60 seconds
|
||||
"USE_UVLOOP": _default,
|
||||
"WEBSOCKET_MAX_SIZE": 2 ** 20, # 1 megabyte
|
||||
"WEBSOCKET_PING_INTERVAL": 20,
|
||||
"WEBSOCKET_PING_TIMEOUT": 20,
|
||||
@@ -79,6 +80,7 @@ class Config(dict, metaclass=DescriptorMeta):
|
||||
REQUEST_TIMEOUT: int
|
||||
RESPONSE_TIMEOUT: int
|
||||
SERVER_NAME: str
|
||||
USE_UVLOOP: Union[Default, bool]
|
||||
WEBSOCKET_MAX_SIZE: int
|
||||
WEBSOCKET_PING_INTERVAL: int
|
||||
WEBSOCKET_PING_TIMEOUT: int
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import asyncio
|
||||
|
||||
from sanic.models.server_types import ConnInfo, Signal
|
||||
from sanic.server.async_server import AsyncioServer
|
||||
from sanic.server.loop import try_use_uvloop
|
||||
from sanic.server.protocols.http_protocol import HttpProtocol
|
||||
from sanic.server.runners import serve, serve_multiple, serve_single
|
||||
|
||||
|
||||
try:
|
||||
import uvloop # type: ignore
|
||||
|
||||
if not isinstance(asyncio.get_event_loop_policy(), uvloop.EventLoopPolicy):
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AsyncioServer",
|
||||
"ConnInfo",
|
||||
@@ -23,4 +13,5 @@ __all__ = (
|
||||
"serve",
|
||||
"serve_multiple",
|
||||
"serve_single",
|
||||
"try_use_uvloop",
|
||||
)
|
||||
|
||||
49
sanic/server/loop.py
Normal file
49
sanic/server/loop.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import asyncio
|
||||
|
||||
from distutils.util import strtobool
|
||||
from os import getenv
|
||||
|
||||
from sanic.compat import OS_IS_WINDOWS
|
||||
from sanic.log import error_logger
|
||||
|
||||
|
||||
def try_use_uvloop() -> None:
|
||||
"""
|
||||
Use uvloop instead of the default asyncio loop.
|
||||
"""
|
||||
if OS_IS_WINDOWS:
|
||||
error_logger.warning(
|
||||
"You are trying to use uvloop, but uvloop is not compatible "
|
||||
"with your system. You can disable uvloop completely by setting "
|
||||
"the 'USE_UVLOOP' configuration value to false, or simply not "
|
||||
"defining it and letting Sanic handle it for you. Sanic will now "
|
||||
"continue to run using the default event loop."
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
import uvloop # type: ignore
|
||||
except ImportError:
|
||||
error_logger.warning(
|
||||
"You are trying to use uvloop, but uvloop is not "
|
||||
"installed in your system. In order to use uvloop "
|
||||
"you must first install it. Otherwise, you can disable "
|
||||
"uvloop completely by setting the 'USE_UVLOOP' "
|
||||
"configuration value to false. Sanic will now continue "
|
||||
"to run with the default event loop."
|
||||
)
|
||||
return
|
||||
|
||||
uvloop_install_removed = strtobool(getenv("SANIC_NO_UVLOOP", "no"))
|
||||
if uvloop_install_removed:
|
||||
error_logger.info(
|
||||
"You are requesting to run Sanic using uvloop, but the "
|
||||
"install-time 'SANIC_NO_UVLOOP' environment variable (used to "
|
||||
"opt-out of installing uvloop with Sanic) is set to true. If "
|
||||
"you want to prevent Sanic from overriding the event loop policy "
|
||||
"during runtime, set the 'USE_UVLOOP' configuration value to "
|
||||
"false."
|
||||
)
|
||||
|
||||
if not isinstance(asyncio.get_event_loop_policy(), uvloop.EventLoopPolicy):
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
@@ -7,8 +7,9 @@ import traceback
|
||||
|
||||
from gunicorn.workers import base # type: ignore
|
||||
|
||||
from sanic.compat import UVLOOP_INSTALLED
|
||||
from sanic.log import logger
|
||||
from sanic.server import HttpProtocol, Signal, serve
|
||||
from sanic.server import HttpProtocol, Signal, serve, try_use_uvloop
|
||||
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
|
||||
|
||||
|
||||
@@ -17,12 +18,8 @@ try:
|
||||
except ImportError:
|
||||
ssl = None # type: ignore
|
||||
|
||||
try:
|
||||
import uvloop # type: ignore
|
||||
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
except ImportError:
|
||||
pass
|
||||
if UVLOOP_INSTALLED:
|
||||
try_use_uvloop()
|
||||
|
||||
|
||||
class GunicornWorker(base.Worker):
|
||||
|
||||
Reference in New Issue
Block a user