From 59cceaf474a9092ecfccff4960c73d12eede8d1b Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 22 Jan 2022 21:10:12 +0100 Subject: [PATCH] Added some basic connection problems handling --- channel.py | 13 +++++++++++-- twitch.py | 38 ++++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/channel.py b/channel.py index cafb318..4390576 100644 --- a/channel.py +++ b/channel.py @@ -9,6 +9,8 @@ from functools import cached_property from datetime import datetime, timezone from typing import Any, Optional, SupportsInt, TYPE_CHECKING +import aiohttp + from exceptions import MinerException from utils import Game, invalidate_cache from constants import JsonType, BASE_URL, GQL_OPERATIONS, ONLINE_DELAY, DROPS_ENABLED_TAG @@ -304,5 +306,12 @@ class Channel: if self._spade_url is None: self._spade_url = await self.get_spade_url() logger.debug(f"Sending minute-watched to {self.name}") - async with self._twitch._session.post(self._spade_url, data=self._payload) as response: - return response.status == 204 + for attempt in range(5): + try: + async with self._twitch._session.post( + self._spade_url, data=self._payload + ) as response: + return response.status == 204 + except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError): + continue + return False diff --git a/twitch.py b/twitch.py index e898778..8a361e2 100644 --- a/twitch.py +++ b/twitch.py @@ -7,6 +7,7 @@ from yarl import URL from time import time from itertools import chain from functools import partial +from contextlib import suppress from typing import ( Optional, Union, List, Dict, Set, OrderedDict, Callable, Iterable, cast, TYPE_CHECKING ) @@ -309,13 +310,25 @@ class Twitch: break await self._state_change.wait() + async def _watch_sleep(self, delay: float) -> None: + # we use wait_for here to allow an asyncio.sleep that can be ended prematurely, + # without cancelling the containing task + self._watching_restart.clear() + with suppress(asyncio.TimeoutError): + await asyncio.wait_for(self._watching_restart.wait(), timeout=delay) + @task_wrapper async def _watch_loop(self) -> None: interval = WATCH_INTERVAL.total_seconds() i = 1 while True: channel = await self.watching_channel.get() - await channel.send_watch() + succeeded = await channel.send_watch() + if not succeeded: + # this usually means there are connection problems + self.gui.print() + await self._watch_sleep(60) + continue last_watch = time() self._drop_update = asyncio.Future() use_active = False @@ -359,15 +372,7 @@ class Twitch: # cleanup channels every hour self.change_state(State.CHANNELS_CLEANUP) i = (i + 1) % 3600 - # we use wait_for here to allow an asyncio.sleep that can be ended prematurely, - # without cancelling the containing task - self._watching_restart.clear() - try: - await asyncio.wait_for( - self._watching_restart.wait(), timeout=last_watch + interval - time() - ) - except asyncio.TimeoutError: - pass + await self._watch_sleep(last_watch + interval - time()) def can_watch(self, channel: Channel) -> bool: if self.game is None: @@ -712,10 +717,15 @@ class Twitch: "Client-Id": CLIENT_ID, } gql_logger.debug(f"GQL Request: {op}") - async with self._session.post(GQL_URL, json=op, headers=headers) as response: - response_json = await response.json() - gql_logger.debug(f"GQL Response: {response_json}") - return response_json + for attempt in range(5): + try: + async with self._session.post(GQL_URL, json=op, headers=headers) as response: + response_json = await response.json() + gql_logger.debug(f"GQL Response: {response_json}") + return response_json + except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError): + continue + raise RuntimeError(f"Ran out of attempts while handling a GQL request: {op}") async def fetch_campaign(self, campaign_id: str, claimed_benefits: Set[str]) -> DropsCampaign: response = await self.gql_request(