Skip to content

Commit fc895c4

Browse files
committed
Fix system routes polluting the middleware cache
1 parent a9a0d84 commit fc895c4

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

CHANGES/9852.bugfix.rst

Lines changed: 1 addition & 0 deletions
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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import logging
33
import warnings
4-
from functools import cache, partial, update_wrapper
4+
from functools import lru_cache, partial, update_wrapper
55
from typing import (
66
TYPE_CHECKING,
77
Any,
@@ -44,6 +44,7 @@
4444
MaskDomain,
4545
MatchedSubAppResource,
4646
PrefixedSubAppResource,
47+
SystemRoute,
4748
UrlDispatcher,
4849
)
4950

@@ -70,7 +71,6 @@
7071
_Resource = TypeVar("_Resource", bound=AbstractResource)
7172

7273

73-
@cache
7474
def _build_middlewares(
7575
handler: Handler, apps: Tuple["Application", ...]
7676
) -> Callable[[Request], Awaitable[StreamResponse]]:
@@ -84,6 +84,9 @@ def _build_middlewares(
8484
return handler
8585

8686

87+
_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)
88+
89+
8790
@final
8891
class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
8992
__slots__ = (
@@ -397,7 +400,13 @@ async def _handle(self, request: Request) -> StreamResponse:
397400
handler = match_info.handler
398401

399402
if self._run_middlewares:
400-
handler = _build_middlewares(handler, match_info.apps)
403+
# If its a SystemRoute, don't cache building the middlewares since
404+
# they are constructed for every MatchInfoError as a new handler
405+
# is made each time.
406+
if isinstance(match_info.route, SystemRoute):
407+
handler = _build_middlewares(handler, match_info.apps)
408+
else:
409+
handler = _cached_build_middleware(handler, match_info.apps)
401410

402411
return await handler(request)
403412

tests/test_web_middleware.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from yarl import URL
66

7-
from aiohttp import web
7+
from aiohttp import web, web_app
88
from aiohttp.pytest_plugin import AiohttpClient
99
from aiohttp.test_utils import TestClient
1010
from aiohttp.typedefs import Handler, Middleware
@@ -522,3 +522,26 @@ async def call(self, request: web.Request, handler: Handler) -> web.Response:
522522
assert 201 == resp.status
523523
txt = await resp.text()
524524
assert "OK[new style middleware]" == txt
525+
526+
527+
async def test_middleware_does_not_leak(aiohttp_client: AiohttpClient) -> None:
528+
async def any_handler(request: web.Request) -> NoReturn:
529+
assert False
530+
531+
class 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)