Perf improv (#2074)

* handler improvements for performance

* Resovle tests

* Linting

* Add tests
This commit is contained in:
Adam Hopkins 2021-03-21 09:47:21 +02:00 committed by GitHub
parent 8a2ea626c6
commit 15a8b5c894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 56 deletions

View File

@ -676,27 +676,23 @@ class Sanic(BaseSanic):
response = None
try:
# Fetch handler from router
(
route,
handler,
kwargs,
) = self.router.get(request)
route, handler, kwargs = self.router.get(
request.path, request.method, request.headers.get("host")
)
request._match_info = kwargs
request.route = route
request.name = route.name
request.uri_template = f"/{route.path}"
request.endpoint = request.name
if (
request.stream
and request.stream.request_body
request.stream.request_body # type: ignore
and not route.ctx.ignore_body
):
if hasattr(handler, "is_stream"):
# Streaming handler: lift the size limit
request.stream.request_max_size = float("inf")
request.stream.request_max_size = float( # type: ignore
"inf"
)
else:
# Non-streaming handler: preload body
await request.receive_body()
@ -730,8 +726,7 @@ class Sanic(BaseSanic):
if response:
response = await request.respond(response)
else:
if request.stream:
response = request.stream.response
response = request.stream.response # type: ignore
# Make sure that response is finished / run StreamingHTTP callback
if isinstance(response, BaseHTTPResponse):
@ -757,9 +752,9 @@ class Sanic(BaseSanic):
):
request.app = self
if not getattr(handler, "__blueprintname__", False):
request.endpoint = handler.__name__
request._name = handler.__name__
else:
request.endpoint = (
request._name = (
getattr(handler, "__blueprintname__", "") + handler.__name__
)

View File

@ -86,15 +86,14 @@ class Request:
"_remote_addr",
"_socket",
"_match_info",
"_name",
"app",
"body",
"conn_info",
"ctx",
"endpoint",
"head",
"headers",
"method",
"name",
"parsed_args",
"parsed_not_grouped_args",
"parsed_files",
@ -106,7 +105,6 @@ class Request:
"route",
"stream",
"transport",
"uri_template",
"version",
)
@ -124,6 +122,7 @@ class Request:
# TODO: Content-Encoding detection
self._parsed_url = parse_url(url_bytes)
self._id: Optional[Union[uuid.UUID, str, int]] = None
self._name: Optional[str] = None
self.app = app
self.headers = headers
@ -136,7 +135,6 @@ class Request:
self.body = b""
self.conn_info: Optional[ConnInfo] = None
self.ctx = SimpleNamespace()
self.name: Optional[str] = None
self.parsed_forwarded: Optional[Options] = None
self.parsed_json = None
self.parsed_form = None
@ -147,12 +145,10 @@ class Request:
self.parsed_not_grouped_args: DefaultDict[
Tuple[bool, bool, str, str], List[Tuple[str, str]]
] = defaultdict(list)
self.uri_template: Optional[str] = None
self.request_middleware_started = False
self._cookies: Optional[Dict[str, str]] = None
self._match_info: Dict[str, Any] = {}
self.stream: Optional[Http] = None
self.endpoint: Optional[str] = None
self.route: Optional[Route] = None
self._protocol = None
@ -207,6 +203,22 @@ class Request:
if not self.body:
self.body = b"".join([data async for data in self.stream])
@property
def name(self):
if self._name:
return self._name
elif self.route:
return self.route.name
return None
@property
def endpoint(self):
return self.name
@property
def uri_template(self):
return f"/{self.route.path}"
@property
def protocol(self):
if not self._protocol:

View File

@ -11,7 +11,6 @@ from sanic_routing.route import Route # type: ignore
from sanic.constants import HTTP_METHODS
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
from sanic.models.handler_types import RouteHandler
from sanic.request import Request
ROUTER_CACHE_SIZE = 1024
@ -27,16 +26,11 @@ class Router(BaseRouter):
DEFAULT_METHOD = "GET"
ALLOWED_METHODS = HTTP_METHODS
# Putting the lru_cache on Router.get() performs better for the benchmarsk
# at tests/benchmark/test_route_resolution_benchmark.py
# However, overall application performance is significantly improved
# with the lru_cache on this method.
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def _get(
self, path, method, host
self, path: str, method: str, host: Optional[str]
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
try:
route, handler, params = self.resolve(
return self.resolve(
path=path,
method=method,
extra={"host": host},
@ -50,14 +44,9 @@ class Router(BaseRouter):
allowed_methods=e.allowed_methods,
)
return (
route,
handler,
params,
)
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def get( # type: ignore
self, request: Request
self, path: str, method: str, host: Optional[str]
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
"""
Retrieve a `Route` object containg the details about how to handle
@ -69,9 +58,7 @@ class Router(BaseRouter):
correct response
:rtype: Tuple[ Route, RouteHandler, Dict[str, Any]]
"""
return self._get(
request.path, request.method, request.headers.get("host")
)
return self._get(path, method, host)
def add( # type: ignore
self,

View File

@ -23,18 +23,21 @@ class TestSanicRouteResolution:
)
router, simple_routes = sanic_router(route_details=simple_routes)
route_to_call = choice(simple_routes)
result = benchmark.pedantic(
router.get,
(
Request(
request = Request(
"/{}".format(route_to_call[-1]).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
),
)
result = benchmark.pedantic(
router.get,
(
request.path,
request.method,
request.headers.get("host"),
),
iterations=1000,
rounds=1000,
@ -56,18 +59,21 @@ class TestSanicRouteResolution:
)
print("{} -> {}".format(route_to_call[-1], url))
result = benchmark.pedantic(
router.get,
(
Request(
request = Request(
"/{}".format(url).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
),
)
result = benchmark.pedantic(
router.get,
(
request.path,
request.method,
request.headers.get("host"),
),
iterations=1000,
rounds=1000,

View File

@ -35,6 +35,27 @@ def test_request_id_defaults_uuid():
assert request.id == request.id == request._id
def test_name_none():
request = Request(b"/", {}, None, "GET", None, None)
assert request.name is None
def test_name_from_route():
request = Request(b"/", {}, None, "GET", None, None)
route = Mock()
request.route = route
assert request.name == route.name
def test_name_from_set():
request = Request(b"/", {}, None, "GET", None, None)
request._name = "foo"
assert request.name == "foo"
@pytest.mark.parametrize(
"request_id,expected_type",
(

View File

@ -166,7 +166,9 @@ def test_matching(path, headers, expected):
request = Request(path, headers, None, "GET", None, app)
try:
app.router.get(request=request)
app.router.get(
request.path, request.method, request.headers.get("host")
)
except NotFound:
response = 404
except Exception: