feat: improve error handling and connection management in DomainFronter and H2Transport

This commit is contained in:
Abolfazl
2026-05-03 06:27:40 +03:30
parent cc906ebbf6
commit 9f8c28a309
2 changed files with 26 additions and 7 deletions
+16 -6
View File
@@ -308,15 +308,22 @@ class DomainFronter:
def _record_h2_failure(self, exc: Exception) -> None:
self._h2_failure_streak += 1
if self._h2_failure_streak >= self._H2_FAILURE_THRESHOLD:
self._h2_disabled_until = time.time() + self._H2_FAILURE_COOLDOWN
# Extend the cooldown window on every failure so a burst of concurrent
# failures doesn't shorten the effective cooldown.
self._h2_disabled_until = max(
self._h2_disabled_until,
time.time() + self._H2_FAILURE_COOLDOWN,
)
# Log exactly once when the threshold is first crossed. Using ==
# (not >=) avoids re-logging on every subsequent failure from
# concurrent in-flight requests that all fail at the same moment.
if self._h2_failure_streak == self._H2_FAILURE_THRESHOLD:
log.warning(
"H2 temporarily disabled for %.0fs after %d consecutive failures (%s)",
self._H2_FAILURE_COOLDOWN,
self._h2_failure_streak,
type(exc).__name__,
)
self._h2_failure_streak = 0
def _stream_download_allowed(self, url: str) -> bool:
host = self._host_key(url)
@@ -1827,8 +1834,11 @@ class DomainFronter:
# Static assets are safe to batch in parallel as independent requests.
is_static = cls._is_static_asset_url(url)
if headers:
# Hard stateful markers: preserve strict ordering / isolation.
if headers and not is_static:
# Static assets (.css, .js, .woff2, .png, …) are served the same
# regardless of cookies — browsers always attach cookies but the
# server doesn't vary static responses on them. Only apply
# header-based stateful checks to non-static URLs.
for name in ("cookie", "authorization", "proxy-authorization"):
if cls._header_value(headers, name):
return True
@@ -1846,7 +1856,7 @@ class DomainFronter:
return True
# Non-static JSON/API calls are treated as stateful by default.
if (not is_static) and "application/json" in accept:
if "application/json" in accept:
return True
return not is_static
+10 -1
View File
@@ -200,8 +200,17 @@ class H2Transport:
_RECONNECT_MIN_INTERVAL = 1.0
async def reconnect(self):
"""Close current connection and re-establish, with backoff."""
"""Close current connection and re-establish, with backoff.
Multiple concurrent callers all queue on _connect_lock. The first
one does the actual work; subsequent callers see _connected=True and
return immediately — otherwise each would tear down the connection
the previous caller just established, causing a reconnect storm.
"""
async with self._connect_lock:
# Another waiter may have already reconnected while we held back.
if self._connected:
return
loop = asyncio.get_running_loop()
elapsed = loop.time() - self._last_reconnect_at
if elapsed < self._RECONNECT_MIN_INTERVAL: