Skip to content

Commit bc15db6

Browse files
authored
[PR #9852/249855a backport][3.10] Fix system routes polluting the middleware cache (#9855)
1 parent 158bf30 commit bc15db6

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

CHANGES/9852.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed system routes polluting the middleware cache -- by :user:`bdraco`.

aiohttp/web_app.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
MaskDomain,
5555
MatchedSubAppResource,
5656
PrefixedSubAppResource,
57+
SystemRoute,
5758
UrlDispatcher,
5859
)
5960

@@ -79,7 +80,6 @@
7980
_Resource = TypeVar("_Resource", bound=AbstractResource)
8081

8182

82-
@lru_cache(None)
8383
def _build_middlewares(
8484
handler: Handler, apps: Tuple["Application", ...]
8585
) -> Callable[[Request], Awaitable[StreamResponse]]:
@@ -90,6 +90,9 @@ def _build_middlewares(
9090
return handler
9191

9292

93+
_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)
94+
95+
9396
class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
9497
ATTRS = frozenset(
9598
[
@@ -544,8 +547,13 @@ async def _handle(self, request: Request) -> StreamResponse:
544547
handler = match_info.handler
545548

546549
if self._run_middlewares:
547-
if not self._has_legacy_middlewares:
548-
handler = _build_middlewares(handler, match_info.apps)
550+
# If its a SystemRoute, don't cache building the middlewares since
551+
# they are constructed for every MatchInfoError as a new handler
552+
# is made each time.
553+
if not self._has_legacy_middlewares and not isinstance(
554+
match_info.route, SystemRoute
555+
):
556+
handler = _cached_build_middleware(handler, match_info.apps)
549557
else:
550558
for app in match_info.apps[::-1]:
551559
for m, new_style in app._middlewares_handlers: # type: ignore[union-attr]

tests/test_web_middleware.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import re
2-
from typing import Any
2+
from typing import Any, NoReturn
33

44
import pytest
55
from yarl import URL
66

7-
from aiohttp import web
7+
from aiohttp import web, web_app
8+
from aiohttp.pytest_plugin import AiohttpClient
89
from aiohttp.typedefs import Handler
910

1011

@@ -520,3 +521,27 @@ async def call(self, request, handler: Handler):
520521
assert 201 == resp.status
521522
txt = await resp.text()
522523
assert "OK[new style middleware]" == txt
524+
525+
526+
async def test_middleware_does_not_leak(aiohttp_client: AiohttpClient) -> None:
527+
async def any_handler(request: web.Request) -> NoReturn:
528+
assert False
529+
530+
class Middleware:
531+
@web.middleware
532+
async def call(
533+
self, request: web.Request, handler: Handler
534+
) -> web.StreamResponse:
535+
return await handler(request)
536+
537+
app = web.Application()
538+
app.router.add_route("POST", "/any", any_handler)
539+
app.middlewares.append(Middleware().call)
540+
541+
client = await aiohttp_client(app)
542+
543+
web_app._cached_build_middleware.cache_clear()
544+
for _ in range(10):
545+
resp = await client.get("/any")
546+
assert resp.status == 405
547+
assert web_app._cached_build_middleware.cache_info().currsize < 10

0 commit comments

Comments
 (0)