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

Implement heapq for cookie expire times #9203

Merged
merged 19 commits into from
Sep 21, 2024
75 changes: 48 additions & 27 deletions aiohttp/cookiejar.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import calendar
import contextlib
import datetime
import heapq
import itertools
import os # noqa
import pathlib
Expand All @@ -10,13 +11,13 @@
import warnings
from collections import defaultdict
from http.cookies import BaseCookie, Morsel, SimpleCookie
from math import ceil
from typing import (
DefaultDict,
Dict,
FrozenSet,
Iterable,
Iterator,
List,
Mapping,
Optional,
Set,
Expand Down Expand Up @@ -106,7 +107,7 @@
for url in treat_as_secure_origin
}
)
self._next_expiration: float = ceil(time.time())
self._expire_heap: List[Tuple[float, Tuple[str, str, str]]] = []
self._expirations: Dict[Tuple[str, str, str], float] = {}

def save(self, file_path: PathLike) -> None:
Expand All @@ -121,34 +122,25 @@

def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None:
if predicate is None:
self._next_expiration = ceil(time.time())
self._expire_heap.clear()
self._cookies.clear()
self._host_only_cookies.clear()
self._expirations.clear()
return

to_del = []
now = time.time()
for (domain, path), cookie in self._cookies.items():
for name, morsel in cookie.items():
key = (domain, path, name)
if (
key in self._expirations and self._expirations[key] <= now
) or predicate(morsel):
to_del.append(key)

for domain, path, name in to_del:
self._host_only_cookies.discard((domain, name))
key = (domain, path, name)
if key in self._expirations:
del self._expirations[(domain, path, name)]
self._cookies[(domain, path)].pop(name, None)

self._next_expiration = (
min(*self._expirations.values(), self.SUB_MAX_TIME) + 1
if self._expirations
else self.MAX_TIME
)
to_del = [
key
for (domain, path), cookie in self._cookies.items()
for name, morsel in cookie.items()
if (
(key := (domain, path, name)) in self._expirations
and self._expirations[key] <= now
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved
)
or predicate(morsel)
]
if to_del:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
self._delete_cookies(to_del)

def clear_domain(self, domain: str) -> None:
self.clear(lambda x: self._is_domain_match(domain, x["domain"]))
Expand All @@ -167,11 +159,40 @@
return sum(len(cookie.values()) for cookie in self._cookies.values())

def _do_expiration(self) -> None:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
self.clear(lambda x: False)
expire_heap_len = len(self._expire_heap)
if not expire_heap_len:
return

if expire_heap_len > 100 and expire_heap_len > len(self._expirations) * 2:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
self._expire_heap = [
bdraco marked this conversation as resolved.
Show resolved Hide resolved
entry
for entry in self._expire_heap
if self._expirations.get(entry[1]) != entry[0]
]
heapq.heapify(self._expire_heap)

Check warning on line 172 in aiohttp/cookiejar.py

View check run for this annotation

Codecov / codecov/patch

aiohttp/cookiejar.py#L172

Added line #L172 was not covered by tests

now = time.time()
to_del: List[Tuple[str, str, str]] = []
while self._expire_heap:
when, cookie_key = self._expire_heap[0]
if when > now:
break
heapq.heappop(self._expire_heap)
if self._expirations.get(cookie_key) == when:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
bdraco marked this conversation as resolved.
Show resolved Hide resolved
to_del.append(cookie_key)
if to_del:
self._delete_cookies(to_del)

def _delete_cookies(self, to_del: List[Tuple[str, str, str]]) -> None:
for domain, path, name in to_del:
self._host_only_cookies.discard((domain, name))
self._cookies[(domain, path)].pop(name, None)
self._expirations.pop((domain, path, name), None)

def _expire_cookie(self, when: float, domain: str, path: str, name: str) -> None:
self._next_expiration = min(self._next_expiration, when)
self._expirations[(domain, path, name)] = when
cookie_key = (domain, path, name)
heapq.heappush(self._expire_heap, (when, cookie_key))
bdraco marked this conversation as resolved.
Show resolved Hide resolved
self._expirations[cookie_key] = when

def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
"""Update cookies."""
Expand Down
Loading