From 73f31c0ccc067118c90fff4f58108e1aab71ecad Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Sun, 4 Feb 2024 17:47:47 +0400 Subject: [PATCH 1/2] fix: restart caching loop automatically upon fail --- brownie/network/middlewares/caching.py | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/brownie/network/middlewares/caching.py b/brownie/network/middlewares/caching.py index f0f38e867..831be6539 100644 --- a/brownie/network/middlewares/caching.py +++ b/brownie/network/middlewares/caching.py @@ -105,17 +105,21 @@ def __init__(self, w3: Web3) -> None: self.cur = Cursor(_get_data_folder().joinpath("cache.db")) self.cur.execute(f"CREATE TABLE IF NOT EXISTS {self.table_key} (method, params, result)") - latest = w3.eth.get_block("latest") + self.lock = threading.Lock() + self.event = threading.Event() + self.start() + + def start(self): + latest = self.w3.eth.get_block("latest") self.last_block = latest.hash self.last_block_seen = latest.timestamp - self.last_request = 0.0 + self.last_request = time.time() self.block_cache: OrderedDict = OrderedDict() - self.block_filter = w3.eth.filter("latest") + self.block_filter = self.w3.eth.filter("latest") - self.lock = threading.Lock() - self.event = threading.Event() self.is_killed = False - threading.Thread(target=self.block_filter_loop, daemon=True).start() + self.loop_thread = threading.Thread(target=self.loop_exception_handler, daemon=True) + self.loop_thread.start() @classmethod def get_layer(cls, w3: Web3, network_type: str) -> Optional[int]: @@ -138,6 +142,14 @@ def get_layer(cls, w3: Web3, network_type: str) -> Optional[int]: def time_since(self) -> float: return time.time() - self.last_request + def loop_exception_handler(self) -> None: + try: + self.block_filter_loop() + except Exception: + # catch unhandled exceptions to avoid random error messages in the console + self.block_cache.clear() + self.is_killed = True + def block_filter_loop(self) -> None: while not self.is_killed: # if the last RPC request was > 60 seconds ago, reduce the rate of updates. @@ -215,6 +227,10 @@ def process_request(self, make_request: Callable, method: str, params: List) -> data = HexBytes(data) return {"id": "cache", "jsonrpc": "2.0", "result": data} + if not self.loop_thread.is_alive(): + # restart the block filter loop if it has crashed (usually from a ConnectionError) + self.start() + with self.lock: self.last_request = time.time() self.event.set() From e7e2577bcc5908bcd6c95eb6ad9ff5640616ed40 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Tue, 6 Feb 2024 00:02:33 +0400 Subject: [PATCH 2/2] fix: set vars inside thread to avoid recursion death --- brownie/network/middlewares/caching.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/brownie/network/middlewares/caching.py b/brownie/network/middlewares/caching.py index 831be6539..c6cb19945 100644 --- a/brownie/network/middlewares/caching.py +++ b/brownie/network/middlewares/caching.py @@ -107,19 +107,13 @@ def __init__(self, w3: Web3) -> None: self.lock = threading.Lock() self.event = threading.Event() - self.start() + self.start_block_filter_loop() - def start(self): - latest = self.w3.eth.get_block("latest") - self.last_block = latest.hash - self.last_block_seen = latest.timestamp - self.last_request = time.time() - self.block_cache: OrderedDict = OrderedDict() - self.block_filter = self.w3.eth.filter("latest") - - self.is_killed = False + def start_block_filter_loop(self): + self.event.clear() self.loop_thread = threading.Thread(target=self.loop_exception_handler, daemon=True) self.loop_thread.start() + self.event.wait() @classmethod def get_layer(cls, w3: Web3, network_type: str) -> Optional[int]: @@ -151,6 +145,16 @@ def loop_exception_handler(self) -> None: self.is_killed = True def block_filter_loop(self) -> None: + # initialize required state variables within the loop to avoid recursion death + latest = self.w3.eth.get_block("latest") + self.last_block = latest.hash + self.last_block_seen = latest.timestamp + self.last_request = time.time() + self.block_cache: OrderedDict = OrderedDict() + self.block_filter = self.w3.eth.filter("latest") + self.is_killed = False + self.event.set() + while not self.is_killed: # if the last RPC request was > 60 seconds ago, reduce the rate of updates. # we eventually settle at one query per minute after 10 minutes of no requests. @@ -229,7 +233,7 @@ def process_request(self, make_request: Callable, method: str, params: List) -> if not self.loop_thread.is_alive(): # restart the block filter loop if it has crashed (usually from a ConnectionError) - self.start() + self.start_block_filter_loop() with self.lock: self.last_request = time.time()