chore: improve exchange_ws startup safety
This commit is contained in:
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from threading import Thread
|
from threading import Event, Thread
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ class ExchangeWS:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self._ccxt_object = ccxt_object
|
self._ccxt_object = ccxt_object
|
||||||
self._background_tasks: set[asyncio.Task] = set()
|
self._background_tasks: set[asyncio.Task] = set()
|
||||||
|
self._loop_ready = Event()
|
||||||
|
|
||||||
self._klines_watching: set[PairWithTimeframe] = set()
|
self._klines_watching: set[PairWithTimeframe] = set()
|
||||||
self._klines_scheduled: set[PairWithTimeframe] = set()
|
self._klines_scheduled: set[PairWithTimeframe] = set()
|
||||||
@@ -33,6 +34,7 @@ class ExchangeWS:
|
|||||||
|
|
||||||
def _start_forever(self) -> None:
|
def _start_forever(self) -> None:
|
||||||
self._loop = asyncio.new_event_loop()
|
self._loop = asyncio.new_event_loop()
|
||||||
|
self._loop_ready.set()
|
||||||
try:
|
try:
|
||||||
self._loop.run_forever()
|
self._loop.run_forever()
|
||||||
finally:
|
finally:
|
||||||
@@ -45,13 +47,24 @@ class ExchangeWS:
|
|||||||
self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||||
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
|
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
|
||||||
self._loop.close()
|
self._loop.close()
|
||||||
|
self._loop_ready.clear()
|
||||||
|
|
||||||
|
def _wait_for_loop(self, timeout: float = 1.0) -> bool:
|
||||||
|
"""
|
||||||
|
Wait for the event loop to be ready
|
||||||
|
Returns True once the loop is ready.
|
||||||
|
Will probably only return false during startup/shutdown.
|
||||||
|
"""
|
||||||
|
if hasattr(self, "_loop"):
|
||||||
|
return True
|
||||||
|
return self._loop_ready.wait(timeout=timeout) and hasattr(self, "_loop")
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
logger.debug("Cleanup called - stopping")
|
logger.debug("Cleanup called - stopping")
|
||||||
self._klines_watching.clear()
|
self._klines_watching.clear()
|
||||||
for task in self._background_tasks:
|
for task in self._background_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
if hasattr(self, "_loop") and not self._loop.is_closed():
|
if self._wait_for_loop(timeout=0.2) and not self._loop.is_closed():
|
||||||
self.reset_connections(cleanup=True)
|
self.reset_connections(cleanup=True)
|
||||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||||
self._thread.join(timeout=5)
|
self._thread.join(timeout=5)
|
||||||
@@ -63,7 +76,7 @@ class ExchangeWS:
|
|||||||
"""
|
"""
|
||||||
Reset all connections - avoids "connection-reset" errors that happen after ~9 days
|
Reset all connections - avoids "connection-reset" errors that happen after ~9 days
|
||||||
"""
|
"""
|
||||||
if hasattr(self, "_loop") and not self._loop.is_closed():
|
if self._wait_for_loop() and not self._loop.is_closed():
|
||||||
logger.info(f"{'Cleaning up' if cleanup else 'Resetting'} exchange WS connections.")
|
logger.info(f"{'Cleaning up' if cleanup else 'Resetting'} exchange WS connections.")
|
||||||
try:
|
try:
|
||||||
fut = asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop)
|
fut = asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop)
|
||||||
@@ -202,6 +215,9 @@ class ExchangeWS:
|
|||||||
"""
|
"""
|
||||||
Schedule a pair/timeframe combination to be watched
|
Schedule a pair/timeframe combination to be watched
|
||||||
"""
|
"""
|
||||||
|
if not self._wait_for_loop():
|
||||||
|
logger.warning(f"Websocket loop not ready. Could not schedule {pair}, {timeframe}.")
|
||||||
|
return
|
||||||
self._klines_watching.add((pair, timeframe, candle_type))
|
self._klines_watching.add((pair, timeframe, candle_type))
|
||||||
self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts()
|
self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts()
|
||||||
# asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop)
|
# asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop)
|
||||||
|
|||||||
Reference in New Issue
Block a user