Browse Source

util.CallbackManager: make sure exceptions in cbs are logged

this resolves the existing FIXME
master
SomberNight 2 years ago
parent
commit
5e1e7dd9b3
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 19
      electrum/util.py

19
electrum/util.py

@ -21,6 +21,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import binascii import binascii
import concurrent.futures
import os, sys, re, json import os, sys, re, json
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any, from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any,
@ -1729,11 +1730,12 @@ def randrange(bound: int) -> int:
return secrets.randbelow(bound - 1) + 1 return secrets.randbelow(bound - 1) + 1
class CallbackManager: class CallbackManager(Logger):
# callbacks set by the GUI or any thread # callbacks set by the GUI or any thread
# guarantee: the callbacks will always get triggered from the asyncio thread. # guarantee: the callbacks will always get triggered from the asyncio thread.
def __init__(self): def __init__(self):
Logger.__init__(self)
self.callback_lock = threading.Lock() self.callback_lock = threading.Lock()
self.callbacks = defaultdict(list) # note: needs self.callback_lock self.callbacks = defaultdict(list) # note: needs self.callback_lock
self._running_cb_futs = set() self._running_cb_futs = set()
@ -1759,17 +1761,24 @@ class CallbackManager:
with self.callback_lock: with self.callback_lock:
callbacks = self.callbacks[event][:] callbacks = self.callbacks[event][:]
for callback in callbacks: for callback in callbacks:
# FIXME: if callback throws, we will lose the traceback if asyncio.iscoroutinefunction(callback): # async cb
if asyncio.iscoroutinefunction(callback):
fut = asyncio.run_coroutine_threadsafe(callback(*args), loop) fut = asyncio.run_coroutine_threadsafe(callback(*args), loop)
# keep strong references around to avoid GC issues: # keep strong references around to avoid GC issues:
self._running_cb_futs.add(fut) self._running_cb_futs.add(fut)
fut.add_done_callback(lambda fut_: self._running_cb_futs.remove(fut_)) def on_done(fut_: concurrent.futures.Future):
elif get_running_loop() == loop: assert fut_.done()
self._running_cb_futs.remove(fut_)
if exc := fut_.exception():
self.logger.error(f"cb errored. {event=}. {exc=}", exc_info=exc)
fut.add_done_callback(on_done)
else: # non-async cb
# note: the cb needs to run in the asyncio thread
if get_running_loop() == loop:
# run callback immediately, so that it is guaranteed # run callback immediately, so that it is guaranteed
# to have been executed when this method returns # to have been executed when this method returns
callback(*args) callback(*args)
else: else:
# note: if cb raises, asyncio will log the exception
loop.call_soon_threadsafe(callback, *args) loop.call_soon_threadsafe(callback, *args)

Loading…
Cancel
Save