Skip to content

Commit 3a9583b

Browse files
authored
Merge pull request #1004 from gsalgado/issue-956
trinity: Proxied services now record remote exceptions' tracebacks
2 parents 35d57d7 + 7203e0d commit 3a9583b

File tree

2 files changed

+73
-10
lines changed

2 files changed

+73
-10
lines changed

evm/utils/logging.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import logging
2-
from logging import Logger
32
from typing import Any
43

54
TRACE_LEVEL_NUM = 5
65

76

8-
class TraceLogger(Logger):
9-
10-
def init(self, name: str, level: int) -> None:
11-
Logger.__init__(self, name, level)
7+
class TraceLogger(logging.Logger):
128

139
def trace(self, message: str, *args: Any, **kwargs: Any) -> None:
1410
self.log(TRACE_LEVEL_NUM, message, *args, **kwargs)

trinity/chains/__init__.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@
44
BaseManager,
55
BaseProxy,
66
)
7+
import inspect
78
import os
9+
import traceback
10+
from types import TracebackType
811
from typing import (
12+
Any,
13+
Callable,
14+
List,
915
Type
1016
)
1117

@@ -144,6 +150,65 @@ def initialize_database(chain_config: ChainConfig, chaindb: AsyncChainDB) -> Non
144150
)
145151

146152

153+
class TracebackRecorder:
154+
"""
155+
Wrap the given instance, delegating all attribute accesses to it but if any method call raises
156+
an exception it is converted into a ChainedExceptionWithTraceback that uses exception chaining
157+
in order to retain the traceback that led to the exception in the remote process.
158+
"""
159+
160+
def __init__(self, obj: Any) -> None:
161+
self.obj = obj
162+
163+
def __dir__(self) -> List[str]:
164+
return dir(self.obj)
165+
166+
def __getattr__(self, name: str) -> Any:
167+
attr = getattr(self.obj, name)
168+
if not inspect.ismethod(attr):
169+
return attr
170+
else:
171+
return record_traceback_on_error(attr)
172+
173+
174+
# Need to "type: ignore" here because we run mypy with --disallow-any-generics
175+
def record_traceback_on_error(attr: Callable) -> Callable: # type: ignore
176+
def wrapper(*args: Any, **kwargs: Any) -> Any:
177+
try:
178+
return attr(*args, **kwargs)
179+
except Exception as e:
180+
# This is a bit of a hack based on https://bugs.python.org/issue13831 to record the
181+
# original traceback (as a string, which is picklable unlike traceback instances) in
182+
# the exception that will be sent to the remote process.
183+
raise ChainedExceptionWithTraceback(e, e.__traceback__)
184+
185+
return wrapper
186+
187+
188+
class RemoteTraceback(Exception):
189+
190+
def __init__(self, tb: str) -> None:
191+
self.tb = tb
192+
193+
def __str__(self) -> str:
194+
return self.tb
195+
196+
197+
class ChainedExceptionWithTraceback(Exception):
198+
199+
def __init__(self, exc: Exception, tb: TracebackType) -> None:
200+
self.tb = '\n"""\n%s"""' % ''.join(traceback.format_exception(type(exc), exc, tb))
201+
self.exc = exc
202+
203+
def __reduce__(self) -> Any:
204+
return rebuild_exc, (self.exc, self.tb)
205+
206+
207+
def rebuild_exc(exc, tb): # type: ignore
208+
exc.__cause__ = RemoteTraceback(tb)
209+
return exc
210+
211+
147212
def serve_chaindb(chain_config: ChainConfig, base_db: BaseDB) -> None:
148213
chaindb = AsyncChainDB(base_db)
149214
chain_class: Type[BaseChain]
@@ -167,23 +232,25 @@ class DBManager(BaseManager):
167232

168233
# Typeshed definitions for multiprocessing.managers is incomplete, so ignore them for now:
169234
# https://github.com/python/typeshed/blob/85a788dbcaa5e9e9a62e55f15d44530cd28ba830/stdlib/3/multiprocessing/managers.pyi#L3
170-
DBManager.register('get_db', callable=lambda: base_db, proxytype=DBProxy) # type: ignore
235+
DBManager.register( # type: ignore
236+
'get_db', callable=lambda: TracebackRecorder(base_db), proxytype=DBProxy)
171237

172238
DBManager.register( # type: ignore
173239
'get_chaindb',
174-
callable=lambda: chaindb,
240+
callable=lambda: TracebackRecorder(chaindb),
175241
proxytype=ChainDBProxy,
176242
)
177-
DBManager.register('get_chain', callable=lambda: chain, proxytype=ChainProxy) # type: ignore
243+
DBManager.register( # type: ignore
244+
'get_chain', callable=lambda: TracebackRecorder(chain), proxytype=ChainProxy)
178245

179246
DBManager.register( # type: ignore
180247
'get_headerdb',
181-
callable=lambda: headerdb,
248+
callable=lambda: TracebackRecorder(headerdb),
182249
proxytype=AsyncHeaderDBProxy,
183250
)
184251
DBManager.register( # type: ignore
185252
'get_header_chain',
186-
callable=lambda: header_chain,
253+
callable=lambda: TracebackRecorder(header_chain),
187254
proxytype=AsyncHeaderChainProxy,
188255
)
189256

0 commit comments

Comments
 (0)