3333_DEFAULT_BACKOFF_BASE = 0.2
3434
3535
36- class FailoverConfig (TypedDict , total = False ):
36+ class FailoverOptions (TypedDict , total = False ):
3737 """Region-failover tuning, passed as the ``failover`` argument. Use it as a
3838 plain dict, e.g. ``failover={"max_attempts": 5}``. All keys are optional.
3939
@@ -49,7 +49,7 @@ class FailoverConfig(TypedDict, total=False):
4949 backoff_base : float
5050
5151
52- def failover_attempts (failover : Optional [FailoverConfig ], host : Optional [str ]) -> int :
52+ def failover_attempts (failover : Optional [FailoverOptions ], host : Optional [str ]) -> int :
5353 """Total request attempts including the initial one; 1 means no failover.
5454
5555 With no config (``None``) failover is enabled only for LiveKit Cloud hosts.
@@ -60,7 +60,7 @@ def failover_attempts(failover: Optional[FailoverConfig], host: Optional[str]) -
6060 return max (1 , failover .get ("max_attempts" , _DEFAULT_MAX_ATTEMPTS ))
6161
6262
63- def failover_backoff_base (failover : Optional [FailoverConfig ]) -> float :
63+ def failover_backoff_base (failover : Optional [FailoverOptions ]) -> float :
6464 return (failover or {}).get ("backoff_base" , _DEFAULT_BACKOFF_BASE )
6565
6666
@@ -143,7 +143,13 @@ async def _fetch(
143143 fetch_headers = {
144144 k : v for k , v in headers .items () if k .lower () not in ("content-type" , "content-length" )
145145 }
146- async with session .get (f"{ origin } /settings/regions" , headers = fetch_headers ) as resp :
146+ # Short timeout so a slow/unreachable discovery endpoint doesn't stall
147+ # the failover path.
148+ async with session .get (
149+ f"{ origin } /settings/regions" ,
150+ headers = fetch_headers ,
151+ timeout = aiohttp .ClientTimeout (total = 2 ),
152+ ) as resp :
147153 if resp .status != 200 :
148154 raise RuntimeError (f"region discovery failed: { resp .status } " )
149155 ttl = _parse_max_age (resp .headers .get ("Cache-Control" ))
0 commit comments