def fibo_backoff() -> Iterator[int]: """ Fibonacci backoff, with the first 6 elements consumed. In other words, this starts at 13, 21, .... """ fib = backoff.fibo() for _ in range(6): next(fib) yield from fib
def fibo_long() -> Iterator[int]: f = fibo() for _ in range(5): next(f) yield from f
def test_fibo_max_value(): gen = backoff.fibo(max_value=8) expected = [1, 1, 2, 3, 5, 8, 8, 8] for expect in expected: assert expect == next(gen)
def test_fibo(): gen = backoff.fibo() expected = [1, 1, 2, 3, 5, 8, 13] for expect in expected: assert expect == next(gen)
def fibo_long() -> Iterator[int]: f = backoff.fibo() for _ in range(4): next(f) yield from f
def wait_gen(): # shorten the wait times between retries a little to fit our # scale a little better (aim to give up within 10 s) for n in backoff.fibo(): yield n / 2.0
class Network(ClientModule): client: Parent["Client"] = field(repr=False) last_replies: Runtime[Deque[Reply]] = field( init=False, repr=False, default_factory=lambda: Deque(maxlen=256), ) _session: Runtime[aiohttp.ClientSession] = field( init=False, repr=False, default_factory=aiohttp.ClientSession, ) @property def api(self) -> URL: return URL(self.client.server) / "_matrix" / "client" / "r0" @property def media_api(self) -> URL: return URL(self.client.server) / "_matrix" / "media" / "r0" async def get(self, url: URL, headers: MethHeaders = None) -> Reply: return await self.send(Request("GET", url, None, headers or {})) async def post( self, url: URL, data: ReqData = None, headers: MethHeaders = None, ) -> Reply: return await self.send(Request("POST", url, data, headers or {})) async def put( self, url: URL, data: ReqData = None, headers: MethHeaders = None, ) -> Reply: return await self.send(Request("PUT", url, data, headers or {})) @backoff.on_exception( lambda: backoff.fibo(max_value=60), (ServerError, TimeoutError, asyncio.TimeoutError, aiohttp.ClientError), giveup=lambda e: isinstance(e, ServerError) and not e.can_retry, on_backoff=_on_backoff, on_giveup=_on_giveup, logger=None, ) async def send(self, request: Request) -> Reply: async with try_rewind(request.data): return await self._send_once(request) async def _send_once(self, request: Request) -> Reply: if self.client._terminated: raise RuntimeError(f"{self.client} terminated, create a new one") token = self.client.access_token data = request.data if token: request.headers["Authorization"] = f"Bearer {token}" if isinstance(data, (Seekable, AsyncSeekable)): data = read_chunked_binary(data) elif isinstance(data, dict): data = None resp = await self._session.request( method=request.method, url=str(request.url), data=data, json=request.data if isinstance(request.data, dict) else None, headers=request.headers, ) mime = resp.content_type disp = resp.content_disposition reply = Reply( request=request, status=resp.status, data=resp.content, json=await resp.json() if mime == "application/json" else {}, mime=mime, size=resp.content_length, filename=disp.filename if disp else None, ) try: resp.raise_for_status() except aiohttp.ClientResponseError: error = ServerError.from_reply(reply) reply.error = error raise error else: self.client.debug("{}", reply, depth=3) return reply finally: self.last_replies.appendleft(reply) async def disconnect(self) -> None: await self._session.close()