Skip to content

Commit 7420723

Browse files
committed
Fixes potential cache issues.
1 parent cf24395 commit 7420723

File tree

2 files changed

+67
-10
lines changed

2 files changed

+67
-10
lines changed

async_substrate_interface/async_substrate.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
TYPE_CHECKING,
2323
)
2424

25-
import asyncstdlib as a
2625
from bt_decode import MetadataV15, PortableRegistry, decode as decode_by_type_string
2726
from scalecodec.base import ScaleBytes, ScaleType, RuntimeConfigurationObject
2827
from scalecodec.types import (
@@ -58,7 +57,7 @@
5857
get_next_id,
5958
rng as random,
6059
)
61-
from async_substrate_interface.utils.cache import async_sql_lru_cache
60+
from async_substrate_interface.utils.cache import async_sql_lru_cache, CachedFetcher
6261
from async_substrate_interface.utils.decoding import (
6362
_determine_if_old_runtime_call,
6463
_bt_decode_to_dict_or_list,
@@ -748,6 +747,12 @@ def __init__(
748747
self.registry_type_map = {}
749748
self.type_id_to_name = {}
750749
self._mock = _mock
750+
self._block_hash_fetcher = CachedFetcher(512, self._get_block_hash)
751+
self._parent_hash_fetcher = CachedFetcher(512, self._get_parent_block_hash)
752+
self._runtime_info_fetcher = CachedFetcher(16, self._get_block_runtime_info)
753+
self._runtime_version_for_fetcher = CachedFetcher(
754+
512, self._get_block_runtime_version_for
755+
)
751756

752757
async def __aenter__(self):
753758
if not self._mock:
@@ -1869,9 +1874,8 @@ async def get_metadata(self, block_hash=None) -> MetadataV15:
18691874

18701875
return runtime.metadata_v15
18711876

1872-
@a.lru_cache(maxsize=512)
18731877
async def get_parent_block_hash(self, block_hash):
1874-
return await self._get_parent_block_hash(block_hash)
1878+
return await self._parent_hash_fetcher.execute(block_hash)
18751879

18761880
async def _get_parent_block_hash(self, block_hash):
18771881
block_header = await self.rpc_request("chain_getHeader", [block_hash])
@@ -1916,9 +1920,8 @@ async def get_storage_by_key(self, block_hash: str, storage_key: str) -> Any:
19161920
"Unknown error occurred during retrieval of events"
19171921
)
19181922

1919-
@a.lru_cache(maxsize=16)
19201923
async def get_block_runtime_info(self, block_hash: str) -> dict:
1921-
return await self._get_block_runtime_info(block_hash)
1924+
return await self._runtime_info_fetcher.execute(block_hash)
19221925

19231926
get_block_runtime_version = get_block_runtime_info
19241927

@@ -1929,9 +1932,8 @@ async def _get_block_runtime_info(self, block_hash: str) -> dict:
19291932
response = await self.rpc_request("state_getRuntimeVersion", [block_hash])
19301933
return response.get("result")
19311934

1932-
@a.lru_cache(maxsize=512)
19331935
async def get_block_runtime_version_for(self, block_hash: str):
1934-
return await self._get_block_runtime_version_for(block_hash)
1936+
return await self._runtime_version_for_fetcher.execute(block_hash)
19351937

19361938
async def _get_block_runtime_version_for(self, block_hash: str):
19371939
"""
@@ -2240,9 +2242,8 @@ async def rpc_request(
22402242
else:
22412243
raise SubstrateRequestException(result[payload_id][0])
22422244

2243-
@a.lru_cache(maxsize=512)
22442245
async def get_block_hash(self, block_id: int) -> str:
2245-
return await self._get_block_hash(block_id)
2246+
return await self._block_hash_fetcher.execute(block_id)
22462247

22472248
async def _get_block_hash(self, block_id: int) -> str:
22482249
return (await self.rpc_request("chain_getBlockHash", [block_id]))["result"]

async_substrate_interface/utils/cache.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import asyncio
2+
from collections import OrderedDict
13
import functools
24
import os
35
import pickle
46
import sqlite3
57
from pathlib import Path
8+
from typing import Callable, Any
9+
610
import asyncstdlib as a
711

12+
813
USE_CACHE = True if os.getenv("NO_CACHE") != "1" else False
914
CACHE_LOCATION = (
1015
os.path.expanduser(
@@ -139,3 +144,54 @@ async def inner(self, *args, **kwargs):
139144
return inner
140145

141146
return decorator
147+
148+
149+
class LRUCache:
150+
def __init__(self, max_size: int):
151+
self.max_size = max_size
152+
self.cache = OrderedDict()
153+
154+
def set(self, key, value):
155+
if key in self.cache:
156+
self.cache.move_to_end(key)
157+
self.cache[key] = value
158+
if len(self.cache) > self.max_size:
159+
self.cache.popitem(last=False)
160+
161+
def get(self, key):
162+
if key in self.cache:
163+
# Mark as recently used
164+
self.cache.move_to_end(key)
165+
return self.cache[key]
166+
return None
167+
168+
169+
class CachedFetcher:
170+
def __init__(self, max_size: int, method: Callable):
171+
self._inflight: dict[int, asyncio.Future] = {}
172+
self._method = method
173+
self._cache = LRUCache(max_size=max_size)
174+
175+
async def execute(self, single_arg: Any) -> str:
176+
if item := self._cache.get(single_arg):
177+
return item
178+
179+
if single_arg in self._inflight:
180+
result = await self._inflight[single_arg]
181+
return result
182+
183+
loop = asyncio.get_running_loop()
184+
future = loop.create_future()
185+
self._inflight[single_arg] = future
186+
187+
try:
188+
result = await self._method(single_arg)
189+
self._cache.set(single_arg, result)
190+
future.set_result(result)
191+
return result
192+
except Exception as e:
193+
# Propagate errors
194+
future.set_exception(e)
195+
raise
196+
finally:
197+
self._inflight.pop(single_arg, None)

0 commit comments

Comments
 (0)