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

Index resources in the UrlDispatcher to avoid linear search for most cases #7829

Merged
merged 64 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
f00fa23
Index resources in the UrlDispatcher to avoid linear search on hit
bdraco Nov 12, 2023
e76297d
tweak
bdraco Nov 12, 2023
6fe2952
Update aiohttp/web_urldispatcher.py
bdraco Nov 12, 2023
ffbad86
timeline
bdraco Nov 12, 2023
ee3fc18
match templates with /{x}
bdraco Nov 13, 2023
9b07111
add support for subapps
bdraco Nov 13, 2023
4863246
dry
bdraco Nov 13, 2023
7cbd606
Merge branch 'master' into fast_url_dispatcher
bdraco Nov 13, 2023
16747a1
Merge branch 'master' into fast_url_dispatcher
bdraco Nov 13, 2023
3d0b0f9
only search the matched subapp resources linearly
bdraco Nov 14, 2023
68bcb01
only search the matched subapp resources linearly
bdraco Nov 14, 2023
0d9fc5f
Merge branch 'master' into fast_url_dispatcher
bdraco Nov 14, 2023
b6b482a
improve comment
bdraco Nov 14, 2023
85671bd
improve comment
bdraco Nov 14, 2023
4dade9a
improve comment
bdraco Nov 14, 2023
02a9658
improve comment
bdraco Nov 14, 2023
a380b8d
fix resources registered at /
bdraco Nov 14, 2023
6691003
fixes, tests
bdraco Nov 14, 2023
0fa6cf6
fixes, tests
bdraco Nov 14, 2023
7d4f108
mypy
bdraco Nov 14, 2023
764055c
mypy
bdraco Nov 14, 2023
b1c06ea
mypy
bdraco Nov 14, 2023
816aec9
fix: tests
bdraco Nov 14, 2023
a2ea12b
coverage
bdraco Nov 14, 2023
0ee50bd
adjust test
bdraco Nov 14, 2023
edf2f98
adjust docs, add missing resource types
bdraco Nov 14, 2023
d643685
docs
bdraco Nov 14, 2023
9225f9f
Update CHANGES/7829.misc
bdraco Nov 14, 2023
f838b44
tweak
bdraco Nov 14, 2023
ed34628
Merge remote-tracking branch 'bdraco/fast_url_dispatcher' into fast_u…
bdraco Nov 14, 2023
ea0e619
tweak
bdraco Nov 14, 2023
b4dcc32
tweak
bdraco Nov 14, 2023
0fc6be4
tweak
bdraco Nov 14, 2023
164ed78
tweak
bdraco Nov 14, 2023
c35a789
tweak
bdraco Nov 14, 2023
0e94b98
fix link
bdraco Nov 14, 2023
adba241
Update docs/web_quickstart.rst
bdraco Nov 15, 2023
c4d732c
copy to both places so they do not miss it
bdraco Nov 16, 2023
75bc97b
copy to both places so they do not miss it
bdraco Nov 16, 2023
003ea83
Update aiohttp/web_urldispatcher.py
bdraco Nov 26, 2023
ac41ef9
Update docs/web_reference.rst
bdraco Nov 26, 2023
7baae39
Update aiohttp/web_urldispatcher.py
bdraco Nov 26, 2023
d4eebd1
Update docs/web_reference.rst
bdraco Nov 26, 2023
a609032
Update aiohttp/web_urldispatcher.py
bdraco Nov 26, 2023
47dd886
was used below
bdraco Nov 26, 2023
2bac458
drop dupe
bdraco Nov 26, 2023
d126a82
remove release
bdraco Nov 26, 2023
5810f02
optimize dispatcher
bdraco Nov 26, 2023
1f3aff8
preen
bdraco Nov 26, 2023
01e383b
use raw_path
bdraco Nov 26, 2023
0cda69f
preen
bdraco Nov 26, 2023
c83d3a1
Update 7829.misc
Dreamsorcerer Nov 26, 2023
02381ba
remove unreachable code
bdraco Nov 27, 2023
0ff388e
Merge branch 'master' into fast_url_dispatcher
bdraco Nov 27, 2023
42e3f47
speed up 404 case
bdraco Nov 27, 2023
13cc627
Merge remote-tracking branch 'bdraco/fast_url_dispatcher' into fast_u…
bdraco Nov 27, 2023
01c9353
bind to object
bdraco Nov 27, 2023
e23796e
no point in freezing system routes
bdraco Nov 27, 2023
281edbf
freeze
bdraco Nov 27, 2023
5883444
Revert "freeze"
bdraco Nov 27, 2023
8cdeac1
Revert "no point in freezing system routes"
bdraco Nov 27, 2023
4b8045e
Revert "bind to object"
bdraco Nov 27, 2023
8ed3bdc
Revert "speed up 404 case"
bdraco Nov 27, 2023
162a9ea
reduce optimization
bdraco Nov 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/7829.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Index resources in the UrlDispatcher to avoid linear search on hit
bdraco marked this conversation as resolved.
Show resolved Hide resolved
48 changes: 45 additions & 3 deletions aiohttp/web_urldispatcher.py
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -716,13 +716,20 @@ class PrefixedSubAppResource(PrefixResource):
def __init__(self, prefix: str, app: "Application") -> None:
super().__init__(prefix)
self._app = app
for resource in app.router.resources():
resource.add_prefix(prefix)
self._add_prefix_to_resources(prefix)

def add_prefix(self, prefix: str) -> None:
super().add_prefix(prefix)
for resource in self._app.router.resources():
self._add_prefix_to_resources(prefix)

def _add_prefix_to_resources(self, prefix: str) -> None:
router = self._app.router
for resource in router.resources():
# Since the canonical path of a resource is about
# to change, we need to unindex it and then reindex
router.unindex_resource(resource)
resource.add_prefix(prefix)
router.index_resource(resource)

def url_for(self, *args: str, **kwargs: str) -> URL:
raise RuntimeError(".url_for() is not supported " "by sub-application root")
Expand Down Expand Up @@ -979,8 +986,23 @@ def __init__(self) -> None:
super().__init__()
self._resources: List[AbstractResource] = []
self._named_resources: Dict[str, AbstractResource] = {}
self._resource_index: dict[str, list[AbstractResource]] = {}

async def resolve(self, request: Request) -> UrlMappingMatchInfo:
url_parts = request.rel_url.raw_parts
resource_index = self._resource_index
# Walk the url parts looking for candidates
for i in range(len(url_parts), 0, -1):
url_part = "/" + "/".join(url_parts[1:i])
bdraco marked this conversation as resolved.
Show resolved Hide resolved
if (resource_candidates := resource_index.get(url_part)) is not None:
for candidate in resource_candidates:
if (
match_dict := (await candidate.resolve(request))[0]
) is not None:
return match_dict
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved

# We didn't find any candidates, so we fallback to a linear search
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved

method = request.method
bdraco marked this conversation as resolved.
Show resolved Hide resolved
allowed_methods: Set[str] = set()

Expand Down Expand Up @@ -1049,6 +1071,26 @@ def register_resource(self, resource: AbstractResource) -> None:
)
self._named_resources[name] = resource
self._resources.append(resource)
self.index_resource(resource)

def _get_resource_index_key(self, resource: AbstractResource) -> str:
"""Return a key to index the resource in the resource index."""
canonical = resource.canonical
if "{" in canonical: # strip at the first { to allow for variables
return canonical.partition("{")[0].rstrip("/") or "/"
return canonical

def index_resource(self, resource: AbstractResource) -> None:
"""Add a resource to the resource index."""
resource_key = self._get_resource_index_key(resource)
# There may be multiple resources for a canonical path
# so we use a list to avoid falling back to a full linear search
self._resource_index.setdefault(resource_key, []).append(resource)

def unindex_resource(self, resource: AbstractResource) -> None:
"""Remove a resource from the resource index."""
resource_key = self._get_resource_index_key(resource)
self._resource_index[resource_key].remove(resource)

def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource:
if path and not path.startswith("/"):
Expand Down
Loading