Bugfix and WS improvements

This commit is contained in:
DevilXD committed 2022-02-14 16:21:01 +01:00
1 parent 5a0d06411d
commit e8cb7ca099
2 files changed
+32 -22

No files matched your search

+6 -1
View File
@@ -8,7 +8,9 @@ from functools import wraps
from contextlib import suppress from contextlib import suppress
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Union, List, MutableSet, Iterable, Iterator, Generic, TypeVar from typing import (
Any, Literal, Union, List, MutableSet, Iterable, Iterator, Coroutine, Generic, TypeVar, cast
)
from constants import JsonType from constants import JsonType
@@ -99,6 +101,9 @@ class AwaitableValue(Generic[_V]):
def has_value(self) -> bool: def has_value(self) -> bool:
return self._event.is_set() return self._event.is_set()
def wait(self) -> Coroutine[Any, Any, Literal[True]]:
return cast(Coroutine[Any, Any, Literal[True]], self._event.wait())
def get_with_default(self, default: _D) -> Union[_D, _V]: def get_with_default(self, default: _D) -> Union[_D, _V]:
if self._event.is_set(): if self._event.is_set():
return self._value return self._value
+26 -21
View File
@@ -14,7 +14,7 @@ except ModuleNotFoundError as exc:
raise ImportError("You have to run 'pip install websockets' first") from exc raise ImportError("You have to run 'pip install websockets' first") from exc
from exceptions import MinerException from exceptions import MinerException
from utils import task_wrapper, create_nonce from utils import task_wrapper, create_nonce, AwaitableValue
from constants import ( from constants import (
JsonType, JsonType,
WebsocketTopic, WebsocketTopic,
@@ -27,10 +27,12 @@ from constants import (
if TYPE_CHECKING: if TYPE_CHECKING:
from twitch import Twitch from twitch import Twitch
from typing_extensions import TypeAlias
logger = logging.getLogger("TwitchDrops") logger = logging.getLogger("TwitchDrops")
ws_logger = logging.getLogger("TwitchDrops.websocket") ws_logger = logging.getLogger("TwitchDrops.websocket")
SendResult: TypeAlias = "asyncio.Future[bool]"
class Websocket: class Websocket:
@@ -40,9 +42,7 @@ class Websocket:
# websocket index # websocket index
self._idx: int = index self._idx: int = index
# current websocket connection # current websocket connection
self._ws: Optional[WebSocketClientProtocol] = None self._ws: AwaitableValue[WebSocketClientProtocol] = AwaitableValue()
# set when there's an active websocket connection
self._connected_flag = asyncio.Event()
# set when the websocket needs to reconnect # set when the websocket needs to reconnect
self._reconnect_requested = asyncio.Event() self._reconnect_requested = asyncio.Event()
# set when the topics changed # set when the topics changed
@@ -60,10 +60,10 @@ class Websocket:
@property @property
def connected(self) -> bool: def connected(self) -> bool:
return self._connected_flag.is_set() return self._ws.has_value()
def wait_until_connected(self): def wait_until_connected(self):
return self._connected_flag.wait() return self._ws.wait()
def set_status(self, status: Optional[str] = None, refresh_topics: bool = False): def set_status(self, status: Optional[str] = None, refresh_topics: bool = False):
self._twitch.gui.websockets.update( self._twitch.gui.websockets.update(
@@ -77,9 +77,10 @@ class Websocket:
self._reconnect_requested.set() self._reconnect_requested.set()
async def close(self): async def close(self):
self.set_status("Disconnecting...") ws = self._ws.get_with_default(None)
if self._ws is not None: if ws is not None:
await self._ws.close() self.set_status("Disconnecting...")
await ws.close()
async def start(self): async def start(self):
if self.connected: if self.connected:
@@ -121,20 +122,21 @@ class Websocket:
ws_logger.info(f"Websocket[{self._idx}] connecting...") ws_logger.info(f"Websocket[{self._idx}] connecting...")
# Connect/Reconnect loop # Connect/Reconnect loop
async for websocket in websocket_connect(WEBSOCKET_URL, ssl=True, ping_interval=None): async for websocket in websocket_connect(WEBSOCKET_URL, ssl=True, ping_interval=None):
websocket.BACKOFF_MAX = 3 * 60 # type: ignore # 3 minutes # 3 minutes of max backoff
self._ws = websocket websocket.BACKOFF_MAX = 3 * 60 # type: ignore
self._ws.set(websocket)
self._reconnect_requested.clear()
self.set_status("Connected") self.set_status("Connected")
try: try:
try: try:
self._reconnect_requested.clear()
self._connected_flag.set()
while not self._reconnect_requested.is_set(): while not self._reconnect_requested.is_set():
await self._handle_ping() await self._handle_ping()
await self._handle_topics() await self._handle_topics()
await self._handle_recv() await self._handle_recv()
finally: finally:
self._ws.clear()
self._submitted.clear() self._submitted.clear()
self._connected_flag.clear() self._topics_changed.set() # lets the next ws connection resub to the topics
# A reconnect was requested # A reconnect was requested
except ConnectionClosed as exc: except ConnectionClosed as exc:
if isinstance(exc, ConnectionClosedOK): if isinstance(exc, ConnectionClosedOK):
@@ -143,7 +145,6 @@ class Websocket:
ws_logger.warning(f"Websocket[{self._idx}] got disconnected.") ws_logger.warning(f"Websocket[{self._idx}] got disconnected.")
else: else:
# we closed it - exit # we closed it - exit
self._ws = None
ws_logger.info(f"Websocket[{self._idx}] stopped.") ws_logger.info(f"Websocket[{self._idx}] stopped.")
self.set_status("Disconnected") self.set_status("Disconnected")
return return
@@ -212,9 +213,10 @@ class Websocket:
Gather incoming messages over the timeout specified. Gather incoming messages over the timeout specified.
Note that there's no return value - this modifies `messages` in-place. Note that there's no return value - this modifies `messages` in-place.
""" """
assert self._ws is not None ws = self._ws.get_with_default(None)
assert ws is not None
while True: while True:
raw_message = await self._ws.recv() raw_message = await ws.recv()
message = json.loads(raw_message) message = json.loads(raw_message)
ws_logger.debug(f"Websocket[{self._idx}] received: {message}") ws_logger.debug(f"Websocket[{self._idx}] received: {message}")
messages.append(message) messages.append(message)
@@ -252,11 +254,14 @@ class Websocket:
ws_logger.warning(f"Websocket[{self._idx}] received unknown payload: {message}") ws_logger.warning(f"Websocket[{self._idx}] received unknown payload: {message}")
def add_topics(self, topics_set: Set[WebsocketTopic]): def add_topics(self, topics_set: Set[WebsocketTopic]):
changed: bool = False
while topics_set and len(self.topics) < WS_TOPICS_LIMIT: while topics_set and len(self.topics) < WS_TOPICS_LIMIT:
topic = topics_set.pop() topic = topics_set.pop()
self.topics[str(topic)] = topic self.topics[str(topic)] = topic
changed = True
if changed:
self._topics_changed.set() self._topics_changed.set()
self.set_status(refresh_topics=True) self.set_status(refresh_topics=True)
def remove_topics(self, topics_set: Set[str]): def remove_topics(self, topics_set: Set[str]):
existing = topics_set.intersection(self.topics.keys()) existing = topics_set.intersection(self.topics.keys())
@@ -270,11 +275,11 @@ class Websocket:
self.set_status(refresh_topics=True) self.set_status(refresh_topics=True)
async def send(self, message: JsonType): async def send(self, message: JsonType):
if self._ws is None: ws = self._ws.get_with_default(None)
return assert ws is not None
if message["type"] != "PING": if message["type"] != "PING":
message["nonce"] = create_nonce() message["nonce"] = create_nonce()
await self._ws.send(json.dumps(message, separators=(',', ':'))) await ws.send(json.dumps(message, separators=(',', ':')))
ws_logger.debug(f"Websocket[{self._idx}] sent: {message}") ws_logger.debug(f"Websocket[{self._idx}] sent: {message}")