|
8 | 8 | from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Union
|
9 | 9 | from urllib.parse import ParseResult, quote, urlparse
|
10 | 10 |
|
| 11 | +import asyncio |
11 | 12 | import aiohttp
|
12 | 13 | import getmac
|
13 | 14 |
|
|
18 | 19 |
|
19 | 20 | Listener = Callable[[Event], None]
|
20 | 21 |
|
| 22 | +MAX_REQUEST_ATTEMPT_COUNT = 3 |
| 23 | +REQUEST_RETRY_DELAY_INTERVAL = 0.5 |
21 | 24 |
|
22 | 25 | _LOGGER = getLogger(__name__)
|
23 | 26 |
|
@@ -391,22 +394,47 @@ async def _load_modes(self) -> None:
|
391 | 394 | async def _api_request(self, path: str, method="GET") -> Any:
|
392 | 395 | """Make a Maker API request."""
|
393 | 396 | params = {"access_token": self.token}
|
394 |
| - conn = aiohttp.TCPConnector(ssl=False) |
395 |
| - try: |
396 |
| - async with aiohttp.request( |
397 |
| - method, f"{self.api_url}/{path}", params=params, connector=conn |
398 |
| - ) as resp: |
399 |
| - if resp.status >= 400: |
400 |
| - if resp.status == 401: |
401 |
| - raise InvalidToken() |
402 |
| - else: |
| 397 | + |
| 398 | + attempt = 0 |
| 399 | + while attempt <= MAX_REQUEST_ATTEMPT_COUNT: |
| 400 | + attempt += 1 |
| 401 | + conn = aiohttp.TCPConnector(ssl=False) |
| 402 | + try: |
| 403 | + async with aiohttp.request( |
| 404 | + method, f"{self.api_url}/{path}", params=params, connector=conn |
| 405 | + ) as resp: |
| 406 | + if resp.status >= 400: |
| 407 | + # retry on server errors or request timeout w/ increasing delay |
| 408 | + if resp.status >= 500 or resp.status == 408: |
| 409 | + if attempt < MAX_REQUEST_ATTEMPT_COUNT: |
| 410 | + _LOGGER.debug( |
| 411 | + "%s request to %s failed with code %d: %s. Retrying...", |
| 412 | + method, path, resp.status, resp.reason |
| 413 | + ) |
| 414 | + await asyncio.sleep(attempt * REQUEST_RETRY_DELAY_INTERVAL) |
| 415 | + continue |
| 416 | + |
| 417 | + if resp.status == 401: |
| 418 | + raise InvalidToken() |
| 419 | + else: |
| 420 | + raise RequestError(resp) |
| 421 | + json = await resp.json() |
| 422 | + if "error" in json and json["error"]: |
403 | 423 | raise RequestError(resp)
|
404 |
| - json = await resp.json() |
405 |
| - if "error" in json and json["error"]: |
406 |
| - raise RequestError(resp) |
407 |
| - return json |
408 |
| - finally: |
409 |
| - await conn.close() |
| 424 | + return json |
| 425 | + except (aiohttp.ClientConnectionError, asyncio.TimeoutError) as e: |
| 426 | + # catch connection exceptions to retry w/ increasing delay |
| 427 | + if attempt < MAX_REQUEST_ATTEMPT_COUNT: |
| 428 | + _LOGGER.debug( |
| 429 | + "%s request to %s failed with %s. Retrying...", |
| 430 | + method, path, str(e) |
| 431 | + ) |
| 432 | + await asyncio.sleep(attempt * REQUEST_RETRY_DELAY_INTERVAL) |
| 433 | + continue |
| 434 | + else: |
| 435 | + raise e |
| 436 | + finally: |
| 437 | + await conn.close() |
410 | 438 |
|
411 | 439 | async def _start_server(self) -> None:
|
412 | 440 | """Start an event listener server."""
|
|
0 commit comments