44import functools
55import logging
66import os
7- import pickle
87import sqlite3
98from pathlib import Path
109from typing import Callable , Any , Awaitable , Hashable , Optional
1110
1211import aiosqlite
12+ import dill as pickle
1313
1414
1515USE_CACHE = True if os .getenv ("NO_CACHE" ) != "1" else False
@@ -38,13 +38,11 @@ def __new__(cls, chain_endpoint: str):
3838 cls ._instances [chain_endpoint ] = instance
3939 return instance
4040
41- async def __call__ (self , chain , other_self , func , args , kwargs ) -> Optional [ Any ] :
41+ async def _create_if_not_exists (self , chain : str , table_name : str ) :
4242 async with self ._lock :
4343 if not self ._db :
4444 _ensure_dir ()
4545 self ._db = await aiosqlite .connect (CACHE_LOCATION )
46- table_name = _get_table_name (func )
47- key = None
4846 if not (local_chain := _check_if_local (chain )) or not USE_CACHE :
4947 await self ._db .execute (
5048 f"""
@@ -72,19 +70,24 @@ async def __call__(self, chain, other_self, func, args, kwargs) -> Optional[Any]
7270 """
7371 )
7472 await self ._db .commit ()
75- key = pickle .dumps ((args , kwargs or None ))
76- try :
77- cursor : aiosqlite .Cursor = await self ._db .execute (
78- f"SELECT value FROM { table_name } WHERE key=? AND chain=?" ,
79- (key , chain ),
80- )
81- result = await cursor .fetchone ()
82- await cursor .close ()
83- if result is not None :
84- return pickle .loads (result [0 ])
85- except (pickle .PickleError , sqlite3 .Error ) as e :
86- logger .exception ("Cache error" , exc_info = e )
87- pass
73+ return local_chain
74+
75+ async def __call__ (self , chain , other_self , func , args , kwargs ) -> Optional [Any ]:
76+ table_name = _get_table_name (func )
77+ local_chain = await self ._create_if_not_exists (chain , table_name )
78+ key = pickle .dumps ((args , kwargs or None ))
79+ try :
80+ cursor : aiosqlite .Cursor = await self ._db .execute (
81+ f"SELECT value FROM { table_name } WHERE key=? AND chain=?" ,
82+ (key , chain ),
83+ )
84+ result = await cursor .fetchone ()
85+ await cursor .close ()
86+ if result is not None :
87+ return pickle .loads (result [0 ])
88+ except (pickle .PickleError , sqlite3 .Error ) as e :
89+ logger .exception ("Cache error" , exc_info = e )
90+ pass
8891 result = await func (other_self , * args , ** kwargs )
8992 if not local_chain or not USE_CACHE :
9093 # TODO use a task here
@@ -95,6 +98,59 @@ async def __call__(self, chain, other_self, func, args, kwargs) -> Optional[Any]
9598 await self ._db .commit ()
9699 return result
97100
101+ async def load_runtime_cache (self , chain : str ) -> tuple [dict , dict , dict ]:
102+ block_mapping = {}
103+ block_hash_mapping = {}
104+ version_mapping = {}
105+ tables = {
106+ "rt_cache_block" : block_mapping ,
107+ "rt_cache_block_hash" : block_hash_mapping ,
108+ "rt_cache_version" : version_mapping
109+ }
110+ for table in tables .keys ():
111+ local_chain = await self ._create_if_not_exists (chain , table )
112+ if local_chain :
113+ return {}, {}, {}
114+ for table_name , mapping in tables .items ():
115+ try :
116+ cursor : aiosqlite .Cursor = await self ._db .execute (
117+ f"SELECT key, value FROM { table_name } WHERE chain=?" ,
118+ (chain ,),
119+ )
120+ results = await cursor .fetchall ()
121+ await cursor .close ()
122+ if results is None :
123+ continue
124+ for row in results :
125+ key , value = row
126+ runtime = pickle .loads (value )
127+ mapping [key ] = runtime
128+ except (pickle .PickleError , sqlite3 .Error ) as e :
129+ logger .exception ("Cache error" , exc_info = e )
130+ return {}, {}, {}
131+ return block_mapping , block_hash_mapping , version_mapping
132+
133+ async def dump_runtime_cache (self , chain : str , block_mapping : dict , block_hash_mapping : dict , version_mapping : dict ) -> None :
134+ async with self ._lock :
135+ if not self ._db :
136+ _ensure_dir ()
137+ self ._db = await aiosqlite .connect (CACHE_LOCATION )
138+ tables = {
139+ "rt_cache_block" : block_mapping ,
140+ "rt_cache_block_hash" : block_hash_mapping ,
141+ "rt_cache_version" : version_mapping
142+ }
143+ for table , mapping in tables .items ():
144+ local_chain = await self ._create_if_not_exists (chain , table )
145+ if local_chain :
146+ return None
147+ await self ._db .executemany (
148+ f"INSERT OR REPLACE INTO { table } (key, value, chain) VALUES (?,?,?)" ,
149+ [(key , pickle .dumps (runtime .serialize ()), chain ) for key , runtime in mapping .items ()],
150+ )
151+ await self ._db .commit ()
152+ return None
153+
98154
99155def _ensure_dir ():
100156 path = Path (CACHE_LOCATION ).parent
0 commit comments